Bug 1744524: part 2) Add `WindowContext::GetUserGestureStart` and remove `WindowConte...
[gecko.git] / dom / base / Document.cpp
blobaea40256c8c586260a19a2ef2bd4a3b0000c2291
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 "AutoplayPolicy.h"
26 #include "ErrorList.h"
27 #include "ExpandedPrincipal.h"
28 #include "MainThreadUtils.h"
29 #include "MobileViewportManager.h"
30 #include "NodeUbiReporting.h"
31 #include "PLDHashTable.h"
32 #include "StorageAccessPermissionRequest.h"
33 #include "ThirdPartyUtil.h"
34 #include "domstubs.h"
35 #include "gfxPlatform.h"
36 #include "imgIContainer.h"
37 #include "imgLoader.h"
38 #include "imgRequestProxy.h"
39 #include "js/Value.h"
40 #include "jsapi.h"
41 #include "mozAutoDocUpdate.h"
42 #include "mozIDOMWindow.h"
43 #include "mozIThirdPartyUtil.h"
44 #include "mozilla/AbstractTimelineMarker.h"
45 #include "mozilla/AntiTrackingUtils.h"
46 #include "mozilla/ArrayIterator.h"
47 #include "mozilla/ArrayUtils.h"
48 #include "mozilla/AsyncEventDispatcher.h"
49 #include "mozilla/Base64.h"
50 #include "mozilla/BasePrincipal.h"
51 #include "mozilla/CSSEnabledState.h"
52 #include "mozilla/ContentBlockingAllowList.h"
53 #include "mozilla/ContentBlockingNotifier.h"
54 #include "mozilla/ContentBlockingUserInteraction.h"
55 #include "mozilla/CycleCollectedJSContext.h"
56 #include "mozilla/DebugOnly.h"
57 #include "mozilla/DocLoadingTimelineMarker.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/HTMLEditor.h"
71 #include "mozilla/HoldDropJSObjects.h"
72 #include "mozilla/IdentifierMapEntry.h"
73 #include "mozilla/InputTaskManager.h"
74 #include "mozilla/IntegerRange.h"
75 #include "mozilla/InternalMutationEvent.h"
76 #include "mozilla/Likely.h"
77 #include "mozilla/Logging.h"
78 #include "mozilla/LookAndFeel.h"
79 #include "mozilla/MacroForEach.h"
80 #include "mozilla/Maybe.h"
81 #include "mozilla/MediaFeatureChange.h"
82 #include "mozilla/MediaManager.h"
83 #include "mozilla/MemoryReporting.h"
84 #include "mozilla/NullPrincipal.h"
85 #include "mozilla/OriginAttributes.h"
86 #include "mozilla/OwningNonNull.h"
87 #include "mozilla/PendingAnimationTracker.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/RejectForeignAllowList.h"
99 #include "mozilla/RelativeTo.h"
100 #include "mozilla/RestyleManager.h"
101 #include "mozilla/ReverseIterator.h"
102 #include "mozilla/SMILAnimationController.h"
103 #include "mozilla/SMILTimeContainer.h"
104 #include "mozilla/ScopeExit.h"
105 #include "mozilla/Components.h"
106 #include "mozilla/ServoStyleConsts.h"
107 #include "mozilla/ServoStyleSet.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_plugins.h"
123 #include "mozilla/StaticPrefs_privacy.h"
124 #include "mozilla/StaticPrefs_security.h"
125 #include "mozilla/StaticPrefs_widget.h"
126 #include "mozilla/StaticPresData.h"
127 #include "mozilla/StorageAccess.h"
128 #include "mozilla/StoragePrincipalHelper.h"
129 #include "mozilla/StyleSheet.h"
130 #include "mozilla/Telemetry.h"
131 #include "mozilla/TelemetryHistogramEnums.h"
132 #include "mozilla/TelemetryScalarEnums.h"
133 #include "mozilla/TextControlElement.h"
134 #include "mozilla/TextEditor.h"
135 #include "mozilla/TimelineConsumers.h"
136 #include "mozilla/TypedEnumBits.h"
137 #include "mozilla/URLDecorationStripper.h"
138 #include "mozilla/URLExtraData.h"
139 #include "mozilla/Unused.h"
140 #include "mozilla/css/ImageLoader.h"
141 #include "mozilla/css/Loader.h"
142 #include "mozilla/css/Rule.h"
143 #include "mozilla/css/SheetParsingMode.h"
144 #include "mozilla/dom/AnonymousContent.h"
145 #include "mozilla/dom/BrowserChild.h"
146 #include "mozilla/dom/BrowsingContext.h"
147 #include "mozilla/dom/BrowsingContextGroup.h"
148 #include "mozilla/dom/CDATASection.h"
149 #include "mozilla/dom/CSPDictionariesBinding.h"
150 #include "mozilla/dom/CanonicalBrowsingContext.h"
151 #include "mozilla/dom/ChromeObserver.h"
152 #include "mozilla/dom/ClientInfo.h"
153 #include "mozilla/dom/ClientState.h"
154 #include "mozilla/dom/Comment.h"
155 #include "mozilla/dom/ContentChild.h"
156 #include "mozilla/dom/DOMImplementation.h"
157 #include "mozilla/dom/DOMIntersectionObserver.h"
158 #include "mozilla/dom/DOMStringList.h"
159 #include "mozilla/dom/DocGroup.h"
160 #include "mozilla/dom/DocumentBinding.h"
161 #include "mozilla/dom/DocumentFragment.h"
162 #include "mozilla/dom/DocumentL10n.h"
163 #include "mozilla/dom/DocumentTimeline.h"
164 #include "mozilla/dom/DocumentType.h"
165 #include "mozilla/dom/ElementBinding.h"
166 #include "mozilla/dom/Event.h"
167 #include "mozilla/dom/EventListenerBinding.h"
168 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
169 #include "mozilla/dom/FeaturePolicy.h"
170 #include "mozilla/dom/FeaturePolicyUtils.h"
171 #include "mozilla/dom/FontFaceSet.h"
172 #include "mozilla/dom/FromParser.h"
173 #include "mozilla/dom/HTMLAllCollection.h"
174 #include "mozilla/dom/HTMLBodyElement.h"
175 #include "mozilla/dom/HTMLCollectionBinding.h"
176 #include "mozilla/dom/HTMLDialogElement.h"
177 #include "mozilla/dom/HTMLFormElement.h"
178 #include "mozilla/dom/HTMLIFrameElement.h"
179 #include "mozilla/dom/HTMLImageElement.h"
180 #include "mozilla/dom/HTMLInputElement.h"
181 #include "mozilla/dom/HTMLLinkElement.h"
182 #include "mozilla/dom/HTMLMediaElement.h"
183 #include "mozilla/dom/HTMLMetaElement.h"
184 #include "mozilla/dom/HTMLSharedElement.h"
185 #include "mozilla/dom/HTMLTextAreaElement.h"
186 #include "mozilla/dom/ImageTracker.h"
187 #include "mozilla/dom/Link.h"
188 #include "mozilla/dom/MediaQueryList.h"
189 #include "mozilla/dom/MediaSource.h"
190 #include "mozilla/dom/MutationObservers.h"
191 #include "mozilla/dom/NameSpaceConstants.h"
192 #include "mozilla/dom/Navigator.h"
193 #include "mozilla/dom/NetErrorInfoBinding.h"
194 #include "mozilla/dom/NodeInfo.h"
195 #include "mozilla/dom/NodeIterator.h"
196 #include "mozilla/dom/PContentChild.h"
197 #include "mozilla/dom/PWindowGlobalChild.h"
198 #include "mozilla/dom/PageTransitionEvent.h"
199 #include "mozilla/dom/PageTransitionEventBinding.h"
200 #include "mozilla/dom/Performance.h"
201 #include "mozilla/dom/PermissionMessageUtils.h"
202 #include "mozilla/dom/PostMessageEvent.h"
203 #include "mozilla/dom/ProcessingInstruction.h"
204 #include "mozilla/dom/Promise.h"
205 #include "mozilla/dom/PromiseNativeHandler.h"
206 #include "mozilla/dom/ResizeObserverController.h"
207 #include "mozilla/dom/SVGElement.h"
208 #include "mozilla/dom/SVGDocument.h"
209 #include "mozilla/dom/SVGSVGElement.h"
210 #include "mozilla/dom/SVGUseElement.h"
211 #include "mozilla/dom/ScriptLoader.h"
212 #include "mozilla/dom/ScriptSettings.h"
213 #include "mozilla/dom/Selection.h"
214 #include "mozilla/dom/ServiceWorkerContainer.h"
215 #include "mozilla/dom/ServiceWorkerDescriptor.h"
216 #include "mozilla/dom/ServiceWorkerManager.h"
217 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
218 #include "mozilla/dom/ShadowRoot.h"
219 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
220 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
221 #include "mozilla/dom/StyleSheetList.h"
222 #include "mozilla/dom/TimeoutManager.h"
223 #include "mozilla/dom/Touch.h"
224 #include "mozilla/dom/TouchEvent.h"
225 #include "mozilla/dom/TreeOrderedArrayInlines.h"
226 #include "mozilla/dom/TreeWalker.h"
227 #include "mozilla/dom/URL.h"
228 #include "mozilla/dom/UserActivation.h"
229 #include "mozilla/dom/WindowBinding.h"
230 #include "mozilla/dom/WindowContext.h"
231 #include "mozilla/dom/WindowGlobalChild.h"
232 #include "mozilla/dom/WindowProxyHolder.h"
233 #include "mozilla/dom/WorkerDocumentListener.h"
234 #include "mozilla/dom/XPathEvaluator.h"
235 #include "mozilla/dom/nsCSPContext.h"
236 #include "mozilla/dom/nsCSPUtils.h"
237 #include "mozilla/extensions/WebExtensionPolicy.h"
238 #include "mozilla/fallible.h"
239 #include "mozilla/gfx/BaseCoord.h"
240 #include "mozilla/gfx/BaseSize.h"
241 #include "mozilla/gfx/Coord.h"
242 #include "mozilla/gfx/Point.h"
243 #include "mozilla/gfx/ScaleFactor.h"
244 #include "mozilla/glean/GleanMetrics.h"
245 #include "mozilla/intl/LocaleService.h"
246 #include "mozilla/ipc/IdleSchedulerChild.h"
247 #include "mozilla/ipc/MessageChannel.h"
248 #include "mozilla/net/ChannelEventQueue.h"
249 #include "mozilla/net/CookieJarSettings.h"
250 #include "mozilla/net/NeckoChannelParams.h"
251 #include "mozilla/net/RequestContextService.h"
252 #include "nsAboutProtocolUtils.h"
253 #include "nsAlgorithm.h"
254 #include "nsAttrValue.h"
255 #include "nsAttrValueInlines.h"
256 #include "nsBaseHashtable.h"
257 #include "nsBidiUtils.h"
258 #include "nsCRT.h"
259 #include "nsCSSPropertyID.h"
260 #include "nsCSSProps.h"
261 #include "nsCSSPseudoElements.h"
262 #include "nsCSSRendering.h"
263 #include "nsCanvasFrame.h"
264 #include "nsCaseTreatment.h"
265 #include "nsCharsetSource.h"
266 #include "nsCommandManager.h"
267 #include "nsCommandParams.h"
268 #include "nsComponentManagerUtils.h"
269 #include "nsContentCreatorFunctions.h"
270 #include "nsContentList.h"
271 #include "nsContentPermissionHelper.h"
272 #include "nsContentSecurityUtils.h"
273 #include "nsContentUtils.h"
274 #include "nsCoord.h"
275 #include "nsCycleCollectionNoteChild.h"
276 #include "nsCycleCollectionTraversalCallback.h"
277 #include "nsDOMAttributeMap.h"
278 #include "nsDOMCaretPosition.h"
279 #include "nsDOMNavigationTiming.h"
280 #include "nsDOMString.h"
281 #include "nsDeviceContext.h"
282 #include "nsDocShell.h"
283 #include "nsDocShellLoadTypes.h"
284 #include "nsError.h"
285 #include "nsEscape.h"
286 #include "nsFocusManager.h"
287 #include "nsFrameLoader.h"
288 #include "nsFrameLoaderOwner.h"
289 #include "nsGenericHTMLElement.h"
290 #include "nsGlobalWindowInner.h"
291 #include "nsGlobalWindowOuter.h"
292 #include "nsHTMLCSSStyleSheet.h"
293 #include "nsHTMLDocument.h"
294 #include "nsHTMLStyleSheet.h"
295 #include "nsHtml5Module.h"
296 #include "nsHtml5Parser.h"
297 #include "nsHtml5TreeOpExecutor.h"
298 #include "nsIAsyncShutdown.h"
299 #include "nsIAuthPrompt.h"
300 #include "nsIAuthPrompt2.h"
301 #include "nsIBFCacheEntry.h"
302 #include "nsIBaseWindow.h"
303 #include "nsIBrowserChild.h"
304 #include "nsIBrowserUsage.h"
305 #include "nsICSSLoaderObserver.h"
306 #include "nsICategoryManager.h"
307 #include "nsICertOverrideService.h"
308 #include "nsIContent.h"
309 #include "nsIContentInlines.h"
310 #include "nsIContentPolicy.h"
311 #include "nsIContentSecurityPolicy.h"
312 #include "nsIContentSink.h"
313 #include "nsICookieJarSettings.h"
314 #include "nsICookieService.h"
315 #include "nsIDOMXULCommandDispatcher.h"
316 #include "nsIDocShell.h"
317 #include "nsIDocShellTreeItem.h"
318 #include "nsIDocumentActivity.h"
319 #include "nsIDocumentEncoder.h"
320 #include "nsIDocumentLoader.h"
321 #include "nsIDocumentLoaderFactory.h"
322 #include "nsIDocumentObserver.h"
323 #include "nsIEditingSession.h"
324 #include "nsIEditor.h"
325 #include "nsIEffectiveTLDService.h"
326 #include "nsIFile.h"
327 #include "nsIFileChannel.h"
328 #include "nsIFrame.h"
329 #include "nsIGlobalObject.h"
330 #include "nsIHTMLCollection.h"
331 #include "nsIHttpChannel.h"
332 #include "nsIHttpChannelInternal.h"
333 #include "nsIIOService.h"
334 #include "nsIImageLoadingContent.h"
335 #include "nsIInlineSpellChecker.h"
336 #include "nsIInputStreamChannel.h"
337 #include "nsIInterfaceRequestorUtils.h"
338 #include "nsILayoutHistoryState.h"
339 #include "nsIMultiPartChannel.h"
340 #include "nsIMutationObserver.h"
341 #include "nsINSSErrorsService.h"
342 #include "nsINamed.h"
343 #include "nsINodeList.h"
344 #include "nsIObjectLoadingContent.h"
345 #include "nsIObserverService.h"
346 #include "nsIPermission.h"
347 #include "nsIPrompt.h"
348 #include "nsIPropertyBag2.h"
349 #include "nsIPublicKeyPinningService.h"
350 #include "nsIReferrerInfo.h"
351 #include "nsIRefreshURI.h"
352 #include "nsIRequest.h"
353 #include "nsIRequestContext.h"
354 #include "nsIRunnable.h"
355 #include "nsISHEntry.h"
356 #include "nsIScriptElement.h"
357 #include "nsIScriptError.h"
358 #include "nsIScriptGlobalObject.h"
359 #include "nsIScriptSecurityManager.h"
360 #include "nsISecurityConsoleMessage.h"
361 #include "nsISelectionController.h"
362 #include "nsISerialEventTarget.h"
363 #include "nsISerializable.h"
364 #include "nsISimpleEnumerator.h"
365 #include "nsISiteSecurityService.h"
366 #include "nsISocketProvider.h"
367 #include "nsISpeculativeConnect.h"
368 #include "nsIStructuredCloneContainer.h"
369 #include "nsIThread.h"
370 #include "nsITimedChannel.h"
371 #include "nsITimer.h"
372 #include "nsITransportSecurityInfo.h"
373 #include "nsIURIMutator.h"
374 #include "nsIVariant.h"
375 #include "nsIWeakReference.h"
376 #include "nsIWebNavigation.h"
377 #include "nsIWidget.h"
378 #include "nsIX509Cert.h"
379 #include "nsIX509CertValidity.h"
380 #include "nsIXMLContentSink.h"
381 #include "nsIXULRuntime.h"
382 #include "nsImageLoadingContent.h"
383 #include "nsImportModule.h"
384 #include "nsLanguageAtomService.h"
385 #include "nsLayoutUtils.h"
386 #include "nsNetCID.h"
387 #include "nsNetUtil.h"
388 #include "nsNodeInfoManager.h"
389 #include "nsObjectLoadingContent.h"
390 #include "nsPIDOMWindowInlines.h"
391 #include "nsPIWindowRoot.h"
392 #include "nsPoint.h"
393 #include "nsPointerHashKeys.h"
394 #include "nsPresContext.h"
395 #include "nsQueryFrame.h"
396 #include "nsQueryObject.h"
397 #include "nsRange.h"
398 #include "nsRect.h"
399 #include "nsRefreshDriver.h"
400 #include "nsSandboxFlags.h"
401 #include "nsSerializationHelper.h"
402 #include "nsServiceManagerUtils.h"
403 #include "nsStringFlags.h"
404 #include "nsStyleUtil.h"
405 #include "nsStringIterator.h"
406 #include "nsStyleSheetService.h"
407 #include "nsStyleStruct.h"
408 #include "nsTextNode.h"
409 #include "nsUnicharUtils.h"
410 #include "nsWrapperCache.h"
411 #include "nsWrapperCacheInlines.h"
412 #include "nsXPCOMCID.h"
413 #include "nsXULAppAPI.h"
414 #include "prthread.h"
415 #include "prtime.h"
416 #include "prtypes.h"
417 #include "xpcpublic.h"
419 // XXX Must be included after mozilla/Encoding.h
420 #include "encoding_rs.h"
422 #include "mozilla/dom/XULBroadcastManager.h"
423 #include "mozilla/dom/XULPersist.h"
424 #include "nsIAppWindow.h"
425 #include "nsXULPrototypeDocument.h"
426 #include "nsXULCommandDispatcher.h"
427 #include "nsXULPopupManager.h"
428 #include "nsIDocShellTreeOwner.h"
430 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
431 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
432 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
433 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
435 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
437 mozilla::LazyLogModule gPageCacheLog("PageCache");
438 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
439 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
440 mozilla::LazyLogModule gUseCountersLog("UseCounters");
442 namespace mozilla {
443 namespace dom {
445 class Document::HeaderData {
446 public:
447 HeaderData(nsAtom* aField, const nsAString& aData)
448 : mField(aField), mData(aData) {}
450 ~HeaderData() {
451 // Delete iteratively to avoid blowing up the stack, though it shouldn't
452 // happen in practice.
453 UniquePtr<HeaderData> next = std::move(mNext);
454 while (next) {
455 next = std::move(next->mNext);
459 RefPtr<nsAtom> mField;
460 nsString mData;
461 UniquePtr<HeaderData> mNext;
464 using LinkArray = nsTArray<Link*>;
466 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
467 nullptr;
469 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
470 static LazyLogModule gCspPRLog("CSP");
471 LazyLogModule gUserInteractionPRLog("UserInteraction");
473 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
474 nsIHttpChannel** aHttpChannel) {
475 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
476 if (httpChannel) {
477 httpChannel.forget(aHttpChannel);
478 return NS_OK;
481 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
482 if (!multipart) {
483 *aHttpChannel = nullptr;
484 return NS_OK;
487 nsCOMPtr<nsIChannel> baseChannel;
488 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
489 if (NS_WARN_IF(NS_FAILED(rv))) {
490 return rv;
493 httpChannel = do_QueryInterface(baseChannel);
494 httpChannel.forget(aHttpChannel);
496 return NS_OK;
499 } // namespace dom
501 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
503 IdentifierMapEntry::IdentifierMapEntry(
504 const IdentifierMapEntry::DependentAtomOrString* aKey)
505 : mKey(aKey ? *aKey : nullptr) {}
507 void IdentifierMapEntry::Traverse(
508 nsCycleCollectionTraversalCallback* aCallback) {
509 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
510 "mIdentifierMap mNameContentList");
511 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
513 if (mImageElement) {
514 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
515 "mIdentifierMap mImageElement element");
516 nsIContent* imageElement = mImageElement;
517 aCallback->NoteXPCOMChild(imageElement);
521 bool IdentifierMapEntry::IsEmpty() {
522 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
523 !mImageElement;
526 bool IdentifierMapEntry::HasNameElement() const {
527 return mNameContentList && mNameContentList->Length() != 0;
530 void IdentifierMapEntry::AddContentChangeCallback(
531 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
532 if (!mChangeCallbacks) {
533 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
536 ChangeCallback cc = {aCallback, aData, aForImage};
537 mChangeCallbacks->PutEntry(cc);
540 void IdentifierMapEntry::RemoveContentChangeCallback(
541 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
542 if (!mChangeCallbacks) return;
543 ChangeCallback cc = {aCallback, aData, aForImage};
544 mChangeCallbacks->RemoveEntry(cc);
545 if (mChangeCallbacks->Count() == 0) {
546 mChangeCallbacks = nullptr;
550 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
551 Element* aNewElement,
552 bool aImageOnly) {
553 if (!mChangeCallbacks) return;
555 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
556 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
557 // Don't fire image changes for non-image observers, and don't fire element
558 // changes for image observers when an image override is active.
559 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
560 continue;
563 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
564 iter.Remove();
569 void IdentifierMapEntry::AddIdElement(Element* aElement) {
570 MOZ_ASSERT(aElement, "Must have element");
571 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
573 size_t index = mIdContentList.Insert(*aElement);
574 if (index == 0) {
575 Element* oldElement = mIdContentList->SafeElementAt(1);
576 FireChangeCallbacks(oldElement, aElement);
580 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
581 MOZ_ASSERT(aElement, "Missing element");
583 // This should only be called while the document is in an update.
584 // Assertions near the call to this method guarantee this.
586 // This could fire in OOM situations
587 // Only assert this in HTML documents for now as XUL does all sorts of weird
588 // crap.
589 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
590 mIdContentList->Contains(aElement),
591 "Removing id entry that doesn't exist");
593 // XXXbz should this ever Compact() I guess when all the content is gone
594 // we'll just get cleaned up in the natural order of things...
595 Element* currentElement = mIdContentList->SafeElementAt(0);
596 mIdContentList.RemoveElement(*aElement);
597 if (currentElement == aElement) {
598 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
602 void IdentifierMapEntry::SetImageElement(Element* aElement) {
603 Element* oldElement = GetImageIdElement();
604 mImageElement = aElement;
605 Element* newElement = GetImageIdElement();
606 if (oldElement != newElement) {
607 FireChangeCallbacks(oldElement, newElement, true);
611 void IdentifierMapEntry::ClearAndNotify() {
612 Element* currentElement = mIdContentList->SafeElementAt(0);
613 mIdContentList.Clear();
614 if (currentElement) {
615 FireChangeCallbacks(currentElement, nullptr);
617 mNameContentList = nullptr;
618 if (mImageElement) {
619 SetImageElement(nullptr);
621 mChangeCallbacks = nullptr;
624 namespace dom {
626 class SimpleHTMLCollection final : public nsSimpleContentList,
627 public nsIHTMLCollection {
628 public:
629 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
631 NS_DECL_ISUPPORTS_INHERITED
633 virtual nsINode* GetParentObject() override {
634 return nsSimpleContentList::GetParentObject();
636 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
637 virtual Element* GetElementAt(uint32_t aIndex) override {
638 return mElements.SafeElementAt(aIndex)->AsElement();
641 virtual Element* GetFirstNamedElement(const nsAString& aName,
642 bool& aFound) override {
643 aFound = false;
644 RefPtr<nsAtom> name = NS_Atomize(aName);
645 for (uint32_t i = 0; i < mElements.Length(); i++) {
646 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
647 Element* element = mElements[i]->AsElement();
648 if (element->GetID() == name ||
649 (element->HasName() &&
650 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
651 aFound = true;
652 return element;
655 return nullptr;
658 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
659 AutoTArray<nsAtom*, 8> atoms;
660 for (uint32_t i = 0; i < mElements.Length(); i++) {
661 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
662 Element* element = mElements[i]->AsElement();
664 nsAtom* id = element->GetID();
665 MOZ_ASSERT(id != nsGkAtoms::_empty);
666 if (id && !atoms.Contains(id)) {
667 atoms.AppendElement(id);
670 if (element->HasName()) {
671 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
672 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
673 if (name && !atoms.Contains(name)) {
674 atoms.AppendElement(name);
679 nsString* names = aNames.AppendElements(atoms.Length());
680 for (uint32_t i = 0; i < atoms.Length(); i++) {
681 atoms[i]->ToString(names[i]);
685 virtual JSObject* GetWrapperPreserveColorInternal() override {
686 return nsWrapperCache::GetWrapperPreserveColor();
688 virtual void PreserveWrapperInternal(
689 nsISupports* aScriptObjectHolder) override {
690 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
692 virtual JSObject* WrapObject(JSContext* aCx,
693 JS::Handle<JSObject*> aGivenProto) override {
694 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
697 using nsBaseContentList::Item;
699 private:
700 virtual ~SimpleHTMLCollection() = default;
703 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
704 nsIHTMLCollection)
706 } // namespace dom
708 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
709 if (!mNameContentList) {
710 mNameContentList = new dom::SimpleHTMLCollection(aNode);
713 mNameContentList->AppendElement(aElement);
716 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
717 if (mNameContentList) {
718 mNameContentList->RemoveElement(aElement);
722 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
723 Element* idElement = GetIdElement();
724 return idElement &&
725 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
728 size_t IdentifierMapEntry::SizeOfExcludingThis(
729 MallocSizeOf aMallocSizeOf) const {
730 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
733 // Helper structs for the content->subdoc map
735 class SubDocMapEntry : public PLDHashEntryHdr {
736 public:
737 // Both of these are strong references
738 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
739 dom::Document* mSubDocument;
742 class OnloadBlocker final : public nsIRequest {
743 public:
744 OnloadBlocker() = default;
746 NS_DECL_ISUPPORTS
747 NS_DECL_NSIREQUEST
749 private:
750 ~OnloadBlocker() = default;
753 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
755 NS_IMETHODIMP
756 OnloadBlocker::GetName(nsACString& aResult) {
757 aResult.AssignLiteral("about:document-onload-blocker");
758 return NS_OK;
761 NS_IMETHODIMP
762 OnloadBlocker::IsPending(bool* _retval) {
763 *_retval = true;
764 return NS_OK;
767 NS_IMETHODIMP
768 OnloadBlocker::GetStatus(nsresult* status) {
769 *status = NS_OK;
770 return NS_OK;
773 NS_IMETHODIMP
774 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
775 NS_IMETHODIMP
776 OnloadBlocker::Suspend(void) { return NS_OK; }
777 NS_IMETHODIMP
778 OnloadBlocker::Resume(void) { return NS_OK; }
780 NS_IMETHODIMP
781 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
782 *aLoadGroup = nullptr;
783 return NS_OK;
786 NS_IMETHODIMP
787 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
789 NS_IMETHODIMP
790 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
791 *aLoadFlags = nsIRequest::LOAD_NORMAL;
792 return NS_OK;
795 NS_IMETHODIMP
796 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
797 return GetTRRModeImpl(aTRRMode);
800 NS_IMETHODIMP
801 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
802 return SetTRRModeImpl(aTRRMode);
805 NS_IMETHODIMP
806 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
808 // ==================================================================
810 namespace dom {
812 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
814 Document* ExternalResourceMap::RequestResource(
815 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
816 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
817 // If we ever start allowing non-same-origin loads here, we might need to do
818 // something interesting with aRequestingPrincipal even for the hashtable
819 // gets.
820 MOZ_ASSERT(aURI, "Must have a URI");
821 MOZ_ASSERT(aRequestingNode, "Must have a node");
822 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
823 *aPendingLoad = nullptr;
824 if (mHaveShutDown) {
825 return nullptr;
828 // First, make sure we strip the ref from aURI.
829 nsCOMPtr<nsIURI> clone;
830 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
831 if (NS_FAILED(rv) || !clone) {
832 return nullptr;
835 ExternalResource* resource;
836 mMap.Get(clone, &resource);
837 if (resource) {
838 return resource->mDocument;
841 bool loadStartSucceeded =
842 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
843 if (!loadEntry) {
844 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
846 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
847 aRequestingNode))) {
848 return false;
852 RefPtr<PendingLoad> load(loadEntry.Data());
853 load.forget(aPendingLoad);
854 return true;
856 if (!loadStartSucceeded) {
857 // Make sure we don't thrash things by trying this load again, since
858 // chances are it failed for good reasons (security check, etc).
859 // This must be done outside the WithEntryHandle functor, as it accesses
860 // mPendingLoads.
861 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
864 return nullptr;
867 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
868 nsTArray<RefPtr<Document>> docs(mMap.Count());
869 for (const auto& entry : mMap.Values()) {
870 if (Document* doc = entry->mDocument) {
871 docs.AppendElement(doc);
875 for (auto& doc : docs) {
876 if (aCallback(*doc) == CallState::Stop) {
877 return;
882 void ExternalResourceMap::Traverse(
883 nsCycleCollectionTraversalCallback* aCallback) const {
884 // mPendingLoads will get cleared out as the requests complete, so
885 // no need to worry about those here.
886 for (const auto& entry : mMap) {
887 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
889 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
890 "mExternalResourceMap.mMap entry"
891 "->mDocument");
892 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
894 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
895 "mExternalResourceMap.mMap entry"
896 "->mViewer");
897 aCallback->NoteXPCOMChild(resource->mViewer);
899 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
900 "mExternalResourceMap.mMap entry"
901 "->mLoadGroup");
902 aCallback->NoteXPCOMChild(resource->mLoadGroup);
906 void ExternalResourceMap::HideViewers() {
907 for (const auto& entry : mMap) {
908 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
909 if (viewer) {
910 viewer->Hide();
915 void ExternalResourceMap::ShowViewers() {
916 for (const auto& entry : mMap) {
917 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
918 if (viewer) {
919 viewer->Show();
924 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
925 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
927 if (aFromDoc->IsShowing()) {
928 aToDoc->OnPageShow(true, nullptr);
932 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
933 nsIContentViewer* aViewer,
934 nsILoadGroup* aLoadGroup,
935 Document* aDisplayDocument) {
936 MOZ_ASSERT(aURI, "Unexpected call");
937 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
938 "Must have both or neither");
940 RefPtr<PendingLoad> load;
941 mPendingLoads.Remove(aURI, getter_AddRefs(load));
943 nsresult rv = NS_OK;
945 nsCOMPtr<Document> doc;
946 if (aViewer) {
947 doc = aViewer->GetDocument();
948 NS_ASSERTION(doc, "Must have a document");
950 doc->SetDisplayDocument(aDisplayDocument);
952 // Make sure that hiding our viewer will tear down its presentation.
953 aViewer->SetSticky(false);
955 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
956 if (NS_SUCCEEDED(rv)) {
957 rv = aViewer->Open(nullptr, nullptr);
960 if (NS_FAILED(rv)) {
961 doc = nullptr;
962 aViewer = nullptr;
963 aLoadGroup = nullptr;
967 ExternalResource* newResource =
968 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
970 newResource->mDocument = doc;
971 newResource->mViewer = aViewer;
972 newResource->mLoadGroup = aLoadGroup;
973 if (doc) {
974 if (nsPresContext* pc = doc->GetPresContext()) {
975 pc->RecomputeBrowsingContextDependentData();
977 TransferShowingState(aDisplayDocument, doc);
980 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
981 for (uint32_t i = 0; i < obs.Length(); ++i) {
982 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
983 nullptr);
986 return rv;
989 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
990 nsIRequestObserver)
992 NS_IMETHODIMP
993 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
994 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
995 if (map.HaveShutDown()) {
996 return NS_BINDING_ABORTED;
999 nsCOMPtr<nsIContentViewer> viewer;
1000 nsCOMPtr<nsILoadGroup> loadGroup;
1001 nsresult rv =
1002 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
1004 // Make sure to do this no matter what
1005 nsresult rv2 =
1006 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
1007 if (NS_FAILED(rv)) {
1008 return rv;
1010 if (NS_FAILED(rv2)) {
1011 mTargetListener = nullptr;
1012 return rv2;
1015 return mTargetListener->OnStartRequest(aRequest);
1018 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
1019 nsIRequest* aRequest, nsIContentViewer** aViewer,
1020 nsILoadGroup** aLoadGroup) {
1021 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
1022 *aViewer = nullptr;
1023 *aLoadGroup = nullptr;
1025 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1026 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1028 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1029 if (httpChannel) {
1030 bool requestSucceeded;
1031 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1032 !requestSucceeded) {
1033 // Bail out on this load, since it looks like we have an HTTP error page
1034 return NS_BINDING_ABORTED;
1038 nsAutoCString type;
1039 chan->GetContentType(type);
1041 nsCOMPtr<nsILoadGroup> loadGroup;
1042 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1044 // Give this document its own loadgroup
1045 nsCOMPtr<nsILoadGroup> newLoadGroup =
1046 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1047 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1048 newLoadGroup->SetLoadGroup(loadGroup);
1050 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1051 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1053 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1054 new LoadgroupCallbacks(callbacks);
1055 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1057 // This is some serious hackery cribbed from docshell
1058 nsCOMPtr<nsICategoryManager> catMan =
1059 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
1060 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
1061 nsCString contractId;
1062 nsresult rv =
1063 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
1064 NS_ENSURE_SUCCESS(rv, rv);
1065 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1066 do_GetService(contractId.get());
1067 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1069 nsCOMPtr<nsIContentViewer> viewer;
1070 nsCOMPtr<nsIStreamListener> listener;
1071 rv = docLoaderFactory->CreateInstance(
1072 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1073 getter_AddRefs(listener), getter_AddRefs(viewer));
1074 NS_ENSURE_SUCCESS(rv, rv);
1075 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1077 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1078 if (!parser) {
1079 /// We don't want to deal with the various fake documents yet
1080 return NS_ERROR_NOT_IMPLEMENTED;
1083 // We can't handle HTML and other weird things here yet.
1084 nsIContentSink* sink = parser->GetContentSink();
1085 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1086 if (!xmlSink) {
1087 return NS_ERROR_NOT_IMPLEMENTED;
1090 listener.swap(mTargetListener);
1091 viewer.forget(aViewer);
1092 newLoadGroup.forget(aLoadGroup);
1093 return NS_OK;
1096 NS_IMETHODIMP
1097 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1098 nsIInputStream* aStream,
1099 uint64_t aOffset,
1100 uint32_t aCount) {
1101 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1102 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1103 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1104 return NS_BINDING_ABORTED;
1106 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1109 NS_IMETHODIMP
1110 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1111 nsresult aStatus) {
1112 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1113 if (mTargetListener) {
1114 nsCOMPtr<nsIStreamListener> listener;
1115 mTargetListener.swap(listener);
1116 return listener->OnStopRequest(aRequest, aStatus);
1119 return NS_OK;
1122 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1123 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1124 MOZ_ASSERT(aURI, "Must have a URI");
1125 MOZ_ASSERT(aRequestingNode, "Must have a node");
1126 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1128 nsCOMPtr<nsILoadGroup> loadGroup =
1129 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1131 nsresult rv = NS_OK;
1132 nsCOMPtr<nsIChannel> channel;
1133 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1134 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1135 nsIContentPolicy::TYPE_OTHER,
1136 nullptr, // aPerformanceStorage
1137 loadGroup);
1138 NS_ENSURE_SUCCESS(rv, rv);
1140 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1141 if (httpChannel) {
1142 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1143 Unused << NS_WARN_IF(NS_FAILED(rv));
1146 mURI = aURI;
1148 return channel->AsyncOpen(this);
1151 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1152 nsIInterfaceRequestor)
1154 #define IMPL_SHIM(_i) \
1155 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1157 IMPL_SHIM(nsILoadContext)
1158 IMPL_SHIM(nsIProgressEventSink)
1159 IMPL_SHIM(nsIChannelEventSink)
1161 #undef IMPL_SHIM
1163 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1165 #define TRY_SHIM(_i) \
1166 PR_BEGIN_MACRO \
1167 if (IID_IS(_i)) { \
1168 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1169 if (!real) { \
1170 return NS_NOINTERFACE; \
1172 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1173 shim.forget(aSink); \
1174 return NS_OK; \
1176 PR_END_MACRO
1178 NS_IMETHODIMP
1179 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1180 void** aSink) {
1181 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1182 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1183 return mCallbacks->GetInterface(aIID, aSink);
1186 *aSink = nullptr;
1188 TRY_SHIM(nsILoadContext);
1189 TRY_SHIM(nsIProgressEventSink);
1190 TRY_SHIM(nsIChannelEventSink);
1192 return NS_NOINTERFACE;
1195 #undef TRY_SHIM
1196 #undef IID_IS
1198 ExternalResourceMap::ExternalResource::~ExternalResource() {
1199 if (mViewer) {
1200 mViewer->Close(nullptr);
1201 mViewer->Destroy();
1205 // ==================================================================
1206 // =
1207 // ==================================================================
1209 // If we ever have an nsIDocumentObserver notification for stylesheet title
1210 // changes we should update the list from that instead of overriding
1211 // EnsureFresh.
1212 class DOMStyleSheetSetList final : public DOMStringList {
1213 public:
1214 explicit DOMStyleSheetSetList(Document* aDocument);
1216 void Disconnect() { mDocument = nullptr; }
1218 virtual void EnsureFresh() override;
1220 protected:
1221 Document* mDocument; // Our document; weak ref. It'll let us know if it
1222 // dies.
1225 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1226 : mDocument(aDocument) {
1227 NS_ASSERTION(mDocument, "Must have document!");
1230 void DOMStyleSheetSetList::EnsureFresh() {
1231 MOZ_ASSERT(NS_IsMainThread());
1233 mNames.Clear();
1235 if (!mDocument) {
1236 return; // Spec says "no exceptions", and we have no style sets if we have
1237 // no document, for sure
1240 size_t count = mDocument->SheetCount();
1241 nsAutoString title;
1242 for (size_t index = 0; index < count; index++) {
1243 StyleSheet* sheet = mDocument->SheetAt(index);
1244 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1245 sheet->GetTitle(title);
1246 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1247 return;
1252 // ==================================================================
1253 Document::SelectorCache::SelectorCache(nsIEventTarget* aEventTarget)
1254 : nsExpirationTracker<SelectorCacheKey, 4>(1000, "Document::SelectorCache",
1255 aEventTarget) {}
1257 Document::SelectorCache::~SelectorCache() { AgeAllGenerations(); }
1259 void Document::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector) {
1260 MOZ_ASSERT(NS_IsMainThread());
1261 MOZ_ASSERT(aSelector);
1263 // There is no guarantee that this method won't be re-entered when selector
1264 // matching is ongoing because "memory-pressure" could be notified immediately
1265 // when OOM happens according to the design of nsExpirationTracker.
1266 // The perfect solution is to delete the |aSelector| and its
1267 // RawServoSelectorList in mTable asynchronously.
1268 // We remove these objects synchronously for now because NotifyExpired() will
1269 // never be triggered by "memory-pressure" which is not implemented yet in
1270 // the stage 2 of mozalloc_handle_oom().
1271 // Once these objects are removed asynchronously, we should update the warning
1272 // added in mozalloc_handle_oom() as well.
1273 RemoveObject(aSelector);
1274 mTable.Remove(aSelector->mKey);
1275 delete aSelector;
1278 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1280 // ==================================================================
1281 // =
1282 // ==================================================================
1284 Document::InternalCommandDataHashtable*
1285 Document::sInternalCommandDataHashtable = nullptr;
1287 // static
1288 void Document::Shutdown() {
1289 if (sInternalCommandDataHashtable) {
1290 sInternalCommandDataHashtable->Clear();
1291 delete sInternalCommandDataHashtable;
1292 sInternalCommandDataHashtable = nullptr;
1296 Document::Document(const char* aContentType)
1297 : nsINode(nullptr),
1298 DocumentOrShadowRoot(this),
1299 mCharacterSet(WINDOWS_1252_ENCODING),
1300 mCharacterSetSource(0),
1301 mParentDocument(nullptr),
1302 mCachedRootElement(nullptr),
1303 mNodeInfoManager(nullptr),
1304 #ifdef DEBUG
1305 mStyledLinksCleared(false),
1306 #endif
1307 mBlockAllMixedContent(false),
1308 mBlockAllMixedContentPreloads(false),
1309 mUpgradeInsecureRequests(false),
1310 mUpgradeInsecurePreloads(false),
1311 mDevToolsWatchingDOMMutations(false),
1312 mBidiEnabled(false),
1313 mMayNeedFontPrefsUpdate(true),
1314 mMathMLEnabled(false),
1315 mIsInitialDocumentInWindow(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 mDocURISchemeIsChrome(false),
1336 mInChromeDocShell(false),
1337 mIsDevToolsDocument(false),
1338 mIsSyntheticDocument(false),
1339 mHasLinksToUpdateRunnable(false),
1340 mFlushingPendingLinkUpdates(false),
1341 mMayHaveDOMMutationObservers(false),
1342 mMayHaveAnimationObservers(false),
1343 mHasCSP(false),
1344 mHasUnsafeEvalCSP(false),
1345 mHasUnsafeInlineCSP(false),
1346 mHasCSPDeliveredThroughHeader(false),
1347 mBFCacheDisallowed(false),
1348 mHasHadDefaultView(false),
1349 mStyleSheetChangeEventsEnabled(false),
1350 mShadowRootAttachedEventEnabled(false),
1351 mIsSrcdocDocument(false),
1352 mHasDisplayDocument(false),
1353 mFontFaceSetDirty(true),
1354 mDidFireDOMContentLoaded(true),
1355 mFrameRequestCallbacksScheduled(false),
1356 mIsTopLevelContentDocument(false),
1357 mIsContentDocument(false),
1358 mDidCallBeginLoad(false),
1359 mEncodingMenuDisabled(false),
1360 mLinksEnabled(true),
1361 mIsSVGGlyphsDocument(false),
1362 mInDestructor(false),
1363 mIsGoingAway(false),
1364 mInXBLUpdate(false),
1365 mNeedsReleaseAfterStackRefCntRelease(false),
1366 mStyleSetFilled(false),
1367 mQuirkSheetAdded(false),
1368 mContentEditableSheetAdded(false),
1369 mDesignModeSheetAdded(false),
1370 mSSApplicableStateNotificationPending(false),
1371 mMayHaveTitleElement(false),
1372 mDOMLoadingSet(false),
1373 mDOMInteractiveSet(false),
1374 mDOMCompleteSet(false),
1375 mAutoFocusFired(false),
1376 mScrolledToRefAlready(false),
1377 mChangeScrollPosWhenScrollingToRef(false),
1378 mDelayFrameLoaderInitialization(false),
1379 mSynchronousDOMContentLoaded(false),
1380 mMaybeServiceWorkerControlled(false),
1381 mAllowZoom(false),
1382 mValidScaleFloat(false),
1383 mValidMinScale(false),
1384 mValidMaxScale(false),
1385 mWidthStrEmpty(false),
1386 mParserAborted(false),
1387 mReportedDocumentUseCounters(false),
1388 mHasReportedShadowDOMUsage(false),
1389 mHasDelayedRefreshEvent(false),
1390 mLoadEventFiring(false),
1391 mSkipLoadEventAfterClose(false),
1392 mDisableCookieAccess(false),
1393 mDisableDocWrite(false),
1394 mTooDeepWriteRecursion(false),
1395 mPendingMaybeEditingStateChanged(false),
1396 mHasBeenEditable(false),
1397 mHasWarnedAboutZoom(false),
1398 mIsRunningExecCommand(false),
1399 mSetCompleteAfterDOMContentLoaded(false),
1400 mDidHitCompleteSheetCache(false),
1401 mPendingFullscreenRequests(0),
1402 mXMLDeclarationBits(0),
1403 mOnloadBlockCount(0),
1404 mWriteLevel(0),
1405 mLazyLoadImageCount(0),
1406 mLazyLoadImageStarted(0),
1407 mLazyLoadImageReachViewportLoading(0),
1408 mLazyLoadImageReachViewportLoaded(0),
1409 mContentEditableCount(0),
1410 mEditingState(EditingState::eOff),
1411 mCompatMode(eCompatibility_FullStandards),
1412 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1413 mAncestorIsLoading(false),
1414 mVisibilityState(dom::VisibilityState::Hidden),
1415 mType(eUnknown),
1416 mDefaultElementType(0),
1417 mAllowXULXBL(eTriUnset),
1418 mSkipDTDSecurityChecks(false),
1419 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1420 mSandboxFlags(0),
1421 mPartID(0),
1422 mMarkedCCGeneration(0),
1423 mPresShell(nullptr),
1424 mSubtreeModifiedDepth(0),
1425 mPreloadPictureDepth(0),
1426 mEventsSuppressed(0),
1427 mIgnoreDestructiveWritesCounter(0),
1428 mStaticCloneCount(0),
1429 mWindow(nullptr),
1430 mBFCacheEntry(nullptr),
1431 mInSyncOperationCount(0),
1432 mBlockDOMContentLoaded(0),
1433 mUseCountersInitialized(false),
1434 mShouldReportUseCounters(false),
1435 mShouldSendPageUseCounters(false),
1436 mUserHasInteracted(false),
1437 mHasUserInteractionTimerScheduled(false),
1438 mStackRefCnt(0),
1439 mUpdateNestLevel(0),
1440 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1441 mViewportType(Unknown),
1442 mViewportFit(ViewportFitType::Auto),
1443 mSubDocuments(nullptr),
1444 mHeaderData(nullptr),
1445 mServoRestyleRootDirtyBits(0),
1446 mThrowOnDynamicMarkupInsertionCounter(0),
1447 mIgnoreOpensDuringUnloadCounter(0),
1448 mSavedResolution(1.0f),
1449 mSavedResolutionBeforeMVM(1.0f),
1450 mGeneration(0),
1451 mCachedTabSizeGeneration(0),
1452 mNextFormNumber(0),
1453 mNextControlNumber(0),
1454 mPreloadService(this),
1455 mShouldNotifyFetchSuccess(false),
1456 mShouldNotifyFormOrPasswordRemoved(false) {
1457 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1459 SetIsInDocument();
1460 SetIsConnected(true);
1462 // Create these unconditionally, they will be used to warn about the `zoom`
1463 // property, even if use counters are disabled.
1464 mStyleUseCounters = Servo_UseCounters_Create().Consume();
1466 SetContentType(nsDependentCString(aContentType));
1468 // Start out mLastStyleSheetSet as null, per spec
1469 SetDOMStringToNull(mLastStyleSheetSet);
1471 // void state used to differentiate an empty source from an unselected source
1472 mPreloadPictureFoundSource.SetIsVoid(true);
1474 RecomputeLanguageFromCharset();
1476 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1477 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1480 #ifndef ANDROID
1481 // unused by GeckoView
1482 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1483 if (NS_WARN_IF(!aWin)) {
1484 return false;
1487 nsIURI* uri = aWin->GetDocumentURI();
1488 if (NS_WARN_IF(!uri)) {
1489 return false;
1491 // getSpec is an expensive operation, hence we first check the scheme
1492 // to see if the caller is actually an about: page.
1493 if (!uri->SchemeIs("about")) {
1494 return false;
1497 nsAutoCString aboutSpec;
1498 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1499 NS_ENSURE_SUCCESS(rv, false);
1501 return aboutSpec.EqualsASCII(aSpec);
1503 #endif
1505 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1506 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1507 #ifdef ANDROID
1508 // GeckoView uses data URLs for error pages, so for now just check for any
1509 // error page
1510 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1511 #else
1512 return win && IsAboutErrorPage(win, "neterror");
1513 #endif
1516 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
1517 JSObject* aObject) {
1518 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1519 #ifdef ANDROID
1520 // GeckoView uses data URLs for error pages, so for now just check for any
1521 // error page
1522 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1523 #else
1524 return win && IsAboutErrorPage(win, "httpsonlyerror");
1525 #endif
1528 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1529 bool aIsTemporary, ErrorResult& aError) {
1530 RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError,
1531 Promise::ePropagateUserInteraction);
1532 if (aError.Failed()) {
1533 return nullptr;
1536 nsCOMPtr<nsISupports> info;
1537 nsCOMPtr<nsITransportSecurityInfo> tsi;
1538 nsresult rv = NS_OK;
1539 if (NS_WARN_IF(!mFailedChannel)) {
1540 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1541 return promise.forget();
1544 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1545 if (NS_WARN_IF(NS_FAILED(rv))) {
1546 promise->MaybeReject(rv);
1547 return promise.forget();
1549 nsCOMPtr<nsIURI> failedChannelURI;
1550 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1551 if (!failedChannelURI) {
1552 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1553 return promise.forget();
1556 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1557 if (!innerURI) {
1558 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1559 return promise.forget();
1562 nsAutoCString host;
1563 innerURI->GetAsciiHost(host);
1564 int32_t port;
1565 innerURI->GetPort(&port);
1567 tsi = do_QueryInterface(info);
1568 if (NS_WARN_IF(!tsi)) {
1569 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1570 return promise.forget();
1573 bool isUntrusted = true;
1574 rv = tsi->GetIsUntrusted(&isUntrusted);
1575 if (NS_WARN_IF(NS_FAILED(rv))) {
1576 promise->MaybeReject(rv);
1577 return promise.forget();
1580 bool isDomainMismatch = true;
1581 rv = tsi->GetIsDomainMismatch(&isDomainMismatch);
1582 if (NS_WARN_IF(NS_FAILED(rv))) {
1583 promise->MaybeReject(rv);
1584 return promise.forget();
1587 bool isNotValidAtThisTime = true;
1588 rv = tsi->GetIsNotValidAtThisTime(&isNotValidAtThisTime);
1589 if (NS_WARN_IF(NS_FAILED(rv))) {
1590 promise->MaybeReject(rv);
1591 return promise.forget();
1594 nsCOMPtr<nsIX509Cert> cert;
1595 rv = tsi->GetServerCert(getter_AddRefs(cert));
1596 if (NS_WARN_IF(NS_FAILED(rv))) {
1597 promise->MaybeReject(rv);
1598 return promise.forget();
1600 if (NS_WARN_IF(!cert)) {
1601 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1602 return promise.forget();
1605 uint32_t flags = 0;
1606 if (isUntrusted) {
1607 flags |= nsICertOverrideService::ERROR_UNTRUSTED;
1609 if (isDomainMismatch) {
1610 flags |= nsICertOverrideService::ERROR_MISMATCH;
1612 if (isNotValidAtThisTime) {
1613 flags |= nsICertOverrideService::ERROR_TIME;
1616 if (XRE_IsContentProcess()) {
1617 nsCOMPtr<nsISerializable> certSer = do_QueryInterface(cert);
1618 nsCString certSerialized;
1619 NS_SerializeToString(certSer, certSerialized);
1621 ContentChild* cc = ContentChild::GetSingleton();
1622 MOZ_ASSERT(cc);
1623 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1624 cc->SendAddCertException(certSerialized, flags, host, port, attrs,
1625 aIsTemporary)
1626 ->Then(GetCurrentSerialEventTarget(), __func__,
1627 [promise](const mozilla::MozPromise<
1628 nsresult, mozilla::ipc::ResponseRejectReason,
1629 true>::ResolveOrRejectValue& aValue) {
1630 if (aValue.IsResolve()) {
1631 promise->MaybeResolve(aValue.ResolveValue());
1632 } else {
1633 promise->MaybeRejectWithUndefined();
1636 return promise.forget();
1639 if (XRE_IsParentProcess()) {
1640 nsCOMPtr<nsICertOverrideService> overrideService =
1641 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1642 if (!overrideService) {
1643 promise->MaybeReject(NS_ERROR_FAILURE);
1644 return promise.forget();
1647 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1648 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1649 flags, aIsTemporary);
1650 if (NS_WARN_IF(NS_FAILED(rv))) {
1651 promise->MaybeReject(rv);
1652 return promise.forget();
1655 promise->MaybeResolveWithUndefined();
1656 return promise.forget();
1659 promise->MaybeReject(NS_ERROR_FAILURE);
1660 return promise.forget();
1663 void Document::ReloadWithHttpsOnlyException() {
1664 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
1665 wgc->SendReloadWithHttpsOnlyException();
1669 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1670 nsCOMPtr<nsISupports> info;
1671 nsCOMPtr<nsITransportSecurityInfo> tsi;
1672 nsresult rv = NS_OK;
1673 if (NS_WARN_IF(!mFailedChannel)) {
1674 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1675 return;
1678 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1679 if (NS_WARN_IF(NS_FAILED(rv))) {
1680 aRv.Throw(rv);
1681 return;
1683 tsi = do_QueryInterface(info);
1684 if (NS_WARN_IF(!tsi)) {
1685 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1686 return;
1689 nsAutoString errorCodeString;
1690 rv = tsi->GetErrorCodeString(errorCodeString);
1691 if (NS_WARN_IF(NS_FAILED(rv))) {
1692 aRv.Throw(rv);
1693 return;
1695 aInfo.mErrorCodeString.Assign(errorCodeString);
1698 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1699 JSObject* aObject) {
1700 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1701 #ifdef ANDROID
1702 // GeckoView uses data URLs for error pages, so for now just check for any
1703 // error page
1704 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1705 #else
1706 return win && IsAboutErrorPage(win, "certerror");
1707 #endif
1710 bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
1711 RefPtr<BasePrincipal> principal =
1712 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
1714 if (!principal) {
1715 return false;
1718 // We allow the privilege SSA to be called from system principal.
1719 if (principal->IsSystemPrincipal()) {
1720 return true;
1723 // We only allow calling the privilege SSA from the content script of the
1724 // webcompat extension.
1725 if (auto* policy = principal->ContentScriptAddonPolicy()) {
1726 nsAutoString addonID;
1727 policy->GetId(addonID);
1729 return addonID.EqualsLiteral("webcompat@mozilla.org");
1732 return false;
1735 bool Document::IsErrorPage() const {
1736 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1737 return loadInfo && loadInfo->GetLoadErrorPage();
1740 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1741 ErrorResult& aRv) {
1742 nsCOMPtr<nsISupports> info;
1743 nsCOMPtr<nsITransportSecurityInfo> tsi;
1744 nsresult rv = NS_OK;
1745 if (NS_WARN_IF(!mFailedChannel)) {
1746 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1747 return;
1750 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1751 if (NS_WARN_IF(NS_FAILED(rv))) {
1752 aRv.Throw(rv);
1753 return;
1755 tsi = do_QueryInterface(info);
1756 if (NS_WARN_IF(!tsi)) {
1757 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1758 return;
1761 nsAutoString errorCodeString;
1762 rv = tsi->GetErrorCodeString(errorCodeString);
1763 if (NS_WARN_IF(NS_FAILED(rv))) {
1764 aRv.Throw(rv);
1765 return;
1767 aInfo.mErrorCodeString.Assign(errorCodeString);
1769 rv = tsi->GetIsUntrusted(&aInfo.mIsUntrusted);
1770 if (NS_WARN_IF(NS_FAILED(rv))) {
1771 aRv.Throw(rv);
1772 return;
1775 rv = tsi->GetIsDomainMismatch(&aInfo.mIsDomainMismatch);
1776 if (NS_WARN_IF(NS_FAILED(rv))) {
1777 aRv.Throw(rv);
1778 return;
1781 rv = tsi->GetIsNotValidAtThisTime(&aInfo.mIsNotValidAtThisTime);
1782 if (NS_WARN_IF(NS_FAILED(rv))) {
1783 aRv.Throw(rv);
1784 return;
1787 nsCOMPtr<nsIX509Cert> cert;
1788 nsCOMPtr<nsIX509CertValidity> validity;
1789 rv = tsi->GetServerCert(getter_AddRefs(cert));
1790 if (NS_WARN_IF(NS_FAILED(rv))) {
1791 aRv.Throw(rv);
1792 return;
1794 if (NS_WARN_IF(!cert)) {
1795 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1796 return;
1799 rv = cert->GetValidity(getter_AddRefs(validity));
1800 if (NS_WARN_IF(NS_FAILED(rv))) {
1801 aRv.Throw(rv);
1802 return;
1804 if (NS_WARN_IF(!validity)) {
1805 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1806 return;
1809 PRTime validityResult;
1810 rv = validity->GetNotBefore(&validityResult);
1811 if (NS_WARN_IF(NS_FAILED(rv))) {
1812 aRv.Throw(rv);
1813 return;
1815 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1817 rv = validity->GetNotAfter(&validityResult);
1818 if (NS_WARN_IF(NS_FAILED(rv))) {
1819 aRv.Throw(rv);
1820 return;
1822 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1824 nsAutoString issuerCommonName;
1825 nsAutoString certChainPEMString;
1826 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1827 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1828 int64_t minValidity = 0;
1829 PRTime notBefore, notAfter;
1830 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1831 rv = tsi->GetFailedCertChain(failedCertArray);
1832 if (NS_WARN_IF(NS_FAILED(rv))) {
1833 aRv.Throw(rv);
1834 return;
1837 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1838 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1839 return;
1842 for (const auto& certificate : failedCertArray) {
1843 rv = certificate->GetIssuerCommonName(issuerCommonName);
1844 if (NS_WARN_IF(NS_FAILED(rv))) {
1845 aRv.Throw(rv);
1846 return;
1849 rv = certificate->GetValidity(getter_AddRefs(validity));
1850 if (NS_WARN_IF(NS_FAILED(rv))) {
1851 aRv.Throw(rv);
1852 return;
1854 if (NS_WARN_IF(!validity)) {
1855 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1856 return;
1859 rv = validity->GetNotBefore(&notBefore);
1860 if (NS_WARN_IF(NS_FAILED(rv))) {
1861 aRv.Throw(rv);
1862 return;
1865 rv = validity->GetNotAfter(&notAfter);
1866 if (NS_WARN_IF(NS_FAILED(rv))) {
1867 aRv.Throw(rv);
1868 return;
1871 notBefore = std::max(minValidity, notBefore);
1872 notAfter = std::min(maxValidity, notAfter);
1873 nsTArray<uint8_t> certArray;
1874 rv = certificate->GetRawDER(certArray);
1875 if (NS_WARN_IF(NS_FAILED(rv))) {
1876 aRv.Throw(rv);
1877 return;
1880 nsAutoString der64;
1881 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1882 certArray.Length(), der64);
1883 if (NS_WARN_IF(NS_FAILED(rv))) {
1884 aRv.Throw(rv);
1885 return;
1887 if (!certChainStrings.AppendElement(der64, fallible)) {
1888 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1889 return;
1893 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1894 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1895 aInfo.mCertValidityRangeNotBefore =
1896 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1898 int32_t errorCode;
1899 rv = tsi->GetErrorCode(&errorCode);
1900 if (NS_WARN_IF(NS_FAILED(rv))) {
1901 aRv.Throw(rv);
1902 return;
1905 nsCOMPtr<nsINSSErrorsService> nsserr =
1906 do_GetService("@mozilla.org/nss_errors_service;1");
1907 if (NS_WARN_IF(!nsserr)) {
1908 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1909 return;
1911 nsresult res;
1912 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1913 if (NS_WARN_IF(NS_FAILED(rv))) {
1914 aRv.Throw(rv);
1915 return;
1917 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1918 if (NS_WARN_IF(NS_FAILED(rv))) {
1919 aRv.Throw(rv);
1920 return;
1923 OriginAttributes attrs;
1924 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1925 nsCOMPtr<nsIURI> aURI;
1926 mFailedChannel->GetURI(getter_AddRefs(aURI));
1927 if (XRE_IsContentProcess()) {
1928 ContentChild* cc = ContentChild::GetSingleton();
1929 MOZ_ASSERT(cc);
1930 cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS);
1931 } else {
1932 nsCOMPtr<nsISiteSecurityService> sss =
1933 do_GetService(NS_SSSERVICE_CONTRACTID);
1934 if (NS_WARN_IF(!sss)) {
1935 return;
1937 Unused << NS_WARN_IF(NS_FAILED(
1938 sss->IsSecureURI(aURI, attrs, nullptr, nullptr, &aInfo.mHasHSTS)));
1940 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1941 do_GetService(NS_PKPSERVICE_CONTRACTID);
1942 if (NS_WARN_IF(!pkps)) {
1943 return;
1945 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1948 bool Document::AllowDeprecatedTls() {
1949 return Preferences::GetBool("security.tls.version.enable-deprecated", false);
1952 void Document::SetAllowDeprecatedTls(bool value) {
1953 if (!IsErrorPage()) {
1954 return;
1957 auto docShell = GetDocShell();
1958 if (!docShell) {
1959 return;
1962 auto child = BrowserChild::GetFrom(docShell);
1963 if (!child) {
1964 return;
1967 child->SendSetAllowDeprecatedTls(value);
1970 bool Document::IsAboutPage() const {
1971 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
1972 return principal->SchemeIs("about");
1975 void Document::ConstructUbiNode(void* storage) {
1976 JS::ubi::Concrete<Document>::construct(storage, this);
1979 void Document::LoadEventFired() {
1980 // Object used to collect some telemetry data so we don't need to query for it
1981 // twice.
1982 PageLoadEventTelemetryData pageLoadEventData;
1984 // Accumulate timing data located in each document's realm and report to
1985 // telemetry.
1986 AccumulateJSTelemetry(pageLoadEventData);
1988 // Collect page load timings
1989 AccumulatePageLoadTelemetry(pageLoadEventData);
1991 // Record page load event
1992 RecordPageLoadEventTelemetry(pageLoadEventData);
1994 // Release the JS bytecode cache from its wait on the load event, and
1995 // potentially dispatch the encoding of the bytecode.
1996 if (ScriptLoader()) {
1997 ScriptLoader()->LoadEventFired();
2001 static uint32_t ConvertToUnsignedFromDouble(double aNumber) {
2002 return aNumber < 0 ? 0 : static_cast<uint32_t>(aNumber);
2005 void Document::RecordPageLoadEventTelemetry(
2006 PageLoadEventTelemetryData aEventTelemetryData) {
2007 static bool sTelemetryEventEnabled = false;
2008 if (!sTelemetryEventEnabled) {
2009 sTelemetryEventEnabled = true;
2010 Telemetry::SetEventRecordingEnabled("page_load"_ns, true);
2013 // If the page load time is empty, then the content wasn't something we want
2014 // to report (i.e. not a top level document).
2015 if (!aEventTelemetryData.mPageLoadTime ||
2016 aEventTelemetryData.mPageLoadTime.IsZero()) {
2017 return;
2019 MOZ_ASSERT(IsTopLevelContentDocument());
2021 nsPIDOMWindowOuter* window = GetWindow();
2022 if (!window) {
2023 return;
2026 nsIDocShell* docshell = window->GetDocShell();
2027 if (!docshell) {
2028 return;
2031 nsAutoCString loadTypeStr;
2032 switch (docshell->GetLoadType()) {
2033 case LOAD_NORMAL:
2034 case LOAD_NORMAL_REPLACE:
2035 case LOAD_NORMAL_BYPASS_CACHE:
2036 case LOAD_NORMAL_BYPASS_PROXY:
2037 case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
2038 loadTypeStr.Append("NORMAL");
2039 break;
2040 case LOAD_HISTORY:
2041 loadTypeStr.Append("HISTORY");
2042 break;
2043 case LOAD_RELOAD_NORMAL:
2044 case LOAD_RELOAD_BYPASS_CACHE:
2045 case LOAD_RELOAD_BYPASS_PROXY:
2046 case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
2047 case LOAD_REFRESH:
2048 case LOAD_REFRESH_REPLACE:
2049 case LOAD_RELOAD_CHARSET_CHANGE:
2050 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
2051 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
2052 loadTypeStr.Append("RELOAD");
2053 break;
2054 case LOAD_LINK:
2055 loadTypeStr.Append("LINK");
2056 break;
2057 case LOAD_STOP_CONTENT:
2058 case LOAD_STOP_CONTENT_AND_REPLACE:
2059 loadTypeStr.Append("STOP");
2060 break;
2061 case LOAD_ERROR_PAGE:
2062 loadTypeStr.Append("ERROR");
2063 break;
2064 default:
2065 loadTypeStr.Append("OTHER");
2066 break;
2069 mozilla::glean::perf::PageLoadExtra extra = {
2070 mozilla::Some(ConvertToUnsignedFromDouble(
2071 aEventTelemetryData.mFirstContentfulPaintTime.ToMilliseconds())),
2072 mozilla::Some(ConvertToUnsignedFromDouble(
2073 aEventTelemetryData.mTotalJSExecutionTime.ToMilliseconds())),
2074 mozilla::Some(ConvertToUnsignedFromDouble(
2075 aEventTelemetryData.mPageLoadTime.ToMilliseconds())),
2076 mozilla::Some(loadTypeStr),
2077 mozilla::Some(ConvertToUnsignedFromDouble(
2078 aEventTelemetryData.mResponseStartTime.ToMilliseconds()))};
2079 mozilla::glean::perf::page_load.Record(mozilla::Some(extra));
2082 void Document::AccumulatePageLoadTelemetry(
2083 PageLoadEventTelemetryData& aEventTelemetryDataOut) {
2084 // Interested only in top level documents for real websites that are in the
2085 // foreground.
2086 if (!ShouldIncludeInTelemetry(false) || !IsTopLevelContentDocument() ||
2087 !GetNavigationTiming() ||
2088 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
2089 return;
2092 if (!GetChannel()) {
2093 return;
2096 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
2097 if (!timedChannel) {
2098 return;
2101 TimeStamp responseStart;
2102 timedChannel->GetResponseStart(&responseStart);
2104 TimeStamp navigationStart =
2105 GetNavigationTiming()->GetNavigationStartTimeStamp();
2107 if (!responseStart || !navigationStart) {
2108 return;
2111 nsCString http3Key;
2112 nsCString http3WithPriorityKey;
2113 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
2114 do_QueryInterface(GetChannel());
2115 if (httpChannel) {
2116 uint32_t major;
2117 uint32_t minor;
2118 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
2119 if (major == 3) {
2120 http3Key = "http3"_ns;
2121 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
2122 nsCString header;
2123 if (httpChannel2 &&
2124 NS_SUCCEEDED(
2125 httpChannel2->GetResponseHeader("priority"_ns, header)) &&
2126 !header.IsEmpty()) {
2127 http3WithPriorityKey = "with_priority"_ns;
2128 } else {
2129 http3WithPriorityKey = "without_priority"_ns;
2131 } else if (major == 2) {
2132 bool supportHttp3 = false;
2133 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
2134 supportHttp3 = false;
2136 if (supportHttp3) {
2137 http3Key = "supports_http3"_ns;
2143 // First Contentful Composite
2144 if (TimeStamp firstContentfulComposite =
2145 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
2146 Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS,
2147 navigationStart, firstContentfulComposite);
2149 if (!http3Key.IsEmpty()) {
2150 Telemetry::AccumulateTimeDelta(
2151 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2152 navigationStart, firstContentfulComposite);
2155 if (!http3WithPriorityKey.IsEmpty()) {
2156 Telemetry::AccumulateTimeDelta(
2157 Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
2158 navigationStart, firstContentfulComposite);
2161 Telemetry::AccumulateTimeDelta(
2162 Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS,
2163 responseStart, firstContentfulComposite);
2165 aEventTelemetryDataOut.mFirstContentfulPaintTime =
2166 firstContentfulComposite - navigationStart;
2169 // DOM Content Loaded event
2170 if (TimeStamp dclEventStart =
2171 GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) {
2172 Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS,
2173 navigationStart, dclEventStart);
2174 Telemetry::AccumulateTimeDelta(
2175 Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS,
2176 responseStart, dclEventStart);
2179 // Load event
2180 if (TimeStamp loadEventStart =
2181 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2182 Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS,
2183 navigationStart, loadEventStart);
2184 if (!http3Key.IsEmpty()) {
2185 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2186 http3Key, navigationStart, loadEventStart);
2189 if (!http3WithPriorityKey.IsEmpty()) {
2190 Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
2191 http3WithPriorityKey, navigationStart,
2192 loadEventStart);
2195 Telemetry::AccumulateTimeDelta(
2196 Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
2197 loadEventStart);
2199 aEventTelemetryDataOut.mResponseStartTime = responseStart - navigationStart;
2200 aEventTelemetryDataOut.mPageLoadTime = loadEventStart - navigationStart;
2204 void Document::AccumulateJSTelemetry(
2205 PageLoadEventTelemetryData& aEventTelemetryDataOut) {
2206 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry(false)) {
2207 return;
2210 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2211 return;
2214 AutoJSContext cx;
2215 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2216 JSAutoRealm ar(cx, globalObject);
2217 JS::JSTimers timers = JS::GetJSTimers(cx);
2219 if (!timers.executionTime.IsZero()) {
2220 Telemetry::Accumulate(
2221 Telemetry::JS_PAGELOAD_EXECUTION_MS,
2222 ConvertToUnsignedFromDouble(timers.executionTime.ToMilliseconds()));
2223 aEventTelemetryDataOut.mTotalJSExecutionTime = timers.executionTime;
2226 if (!timers.delazificationTime.IsZero()) {
2227 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_DELAZIFICATION_MS,
2228 ConvertToUnsignedFromDouble(
2229 timers.delazificationTime.ToMilliseconds()));
2232 if (!timers.xdrEncodingTime.IsZero()) {
2233 Telemetry::Accumulate(
2234 Telemetry::JS_PAGELOAD_XDR_ENCODING_MS,
2235 ConvertToUnsignedFromDouble(timers.xdrEncodingTime.ToMilliseconds()));
2238 if (!timers.baselineCompileTime.IsZero()) {
2239 Telemetry::Accumulate(Telemetry::JS_PAGELOAD_BASELINE_COMPILE_MS,
2240 ConvertToUnsignedFromDouble(
2241 timers.baselineCompileTime.ToMilliseconds()));
2244 if (!timers.gcTime.IsZero()) {
2245 Telemetry::Accumulate(
2246 Telemetry::JS_PAGELOAD_GC_MS,
2247 ConvertToUnsignedFromDouble(timers.gcTime.ToMilliseconds()));
2250 if (!timers.protectTime.IsZero()) {
2251 Telemetry::Accumulate(
2252 Telemetry::JS_PAGELOAD_PROTECT_MS,
2253 ConvertToUnsignedFromDouble(timers.protectTime.ToMilliseconds()));
2257 Document::~Document() {
2258 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2259 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2260 "Can't be top-level and a resource doc at the same time");
2262 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2264 if (IsTopLevelContentDocument()) {
2265 RemoveToplevelLoadingDocument(this);
2267 // don't report for about: pages
2268 if (!IsAboutPage()) {
2269 // record CSP telemetry on this document
2270 if (mHasCSP) {
2271 Accumulate(Telemetry::CSP_DOCUMENTS_COUNT, 1);
2273 if (mHasUnsafeInlineCSP) {
2274 Accumulate(Telemetry::CSP_UNSAFE_INLINE_DOCUMENTS_COUNT, 1);
2276 if (mHasUnsafeEvalCSP) {
2277 Accumulate(Telemetry::CSP_UNSAFE_EVAL_DOCUMENTS_COUNT, 1);
2280 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2281 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
2284 if (IsHTMLDocument()) {
2285 switch (GetCompatibilityMode()) {
2286 case eCompatibility_FullStandards:
2287 Telemetry::AccumulateCategorical(
2288 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
2289 break;
2290 case eCompatibility_AlmostStandards:
2291 Telemetry::AccumulateCategorical(
2292 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
2293 break;
2294 case eCompatibility_NavQuirks:
2295 Telemetry::AccumulateCategorical(
2296 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
2297 break;
2298 default:
2299 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
2300 break;
2306 mInDestructor = true;
2307 mInUnlinkOrDeletion = true;
2309 mozilla::DropJSObjects(this);
2311 // Clear mObservers to keep it in sync with the mutationobserver list
2312 mObservers.Clear();
2314 mIntersectionObservers.Clear();
2316 if (mStyleSheetSetList) {
2317 mStyleSheetSetList->Disconnect();
2320 if (mAnimationController) {
2321 mAnimationController->Disconnect();
2324 MOZ_ASSERT(mTimelines.isEmpty());
2326 mParentDocument = nullptr;
2328 // Kill the subdocument map, doing this will release its strong
2329 // references, if any.
2330 delete mSubDocuments;
2331 mSubDocuments = nullptr;
2333 nsAutoScriptBlocker scriptBlocker;
2335 // Destroy link map now so we don't waste time removing
2336 // links one by one
2337 DestroyElementMaps();
2339 // Invalidate cached array of child nodes
2340 InvalidateChildNodes();
2342 // We should not have child nodes when destructor is called,
2343 // since child nodes keep their owner document alive.
2344 MOZ_ASSERT(!HasChildren());
2346 mCachedRootElement = nullptr;
2348 for (auto& sheets : mAdditionalSheets) {
2349 UnlinkStyleSheets(sheets);
2352 if (mAttrStyleSheet) {
2353 mAttrStyleSheet->SetOwningDocument(nullptr);
2356 if (mListenerManager) {
2357 mListenerManager->Disconnect();
2358 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2361 if (mScriptLoader) {
2362 mScriptLoader->DropDocumentReference();
2365 if (mCSSLoader) {
2366 // Could be null here if Init() failed or if we have been unlinked.
2367 mCSSLoader->DropDocumentReference();
2370 if (mStyleImageLoader) {
2371 mStyleImageLoader->DropDocumentReference();
2374 if (mXULBroadcastManager) {
2375 mXULBroadcastManager->DropDocumentReference();
2378 if (mXULPersist) {
2379 mXULPersist->DropDocumentReference();
2382 if (mPermissionDelegateHandler) {
2383 mPermissionDelegateHandler->DropDocumentReference();
2386 mHeaderData = nullptr;
2388 mPendingTitleChangeEvent.Revoke();
2390 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2391 "must not have media query lists left");
2393 if (mNodeInfoManager) {
2394 mNodeInfoManager->DropDocumentReference();
2397 if (mDocGroup) {
2398 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2399 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2402 UnlinkOriginalDocumentIfStatic();
2404 UnregisterFromMemoryReportingForDataDocument();
2407 NS_INTERFACE_TABLE_HEAD(Document)
2408 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2409 NS_INTERFACE_TABLE_BEGIN
2410 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2411 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2412 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2413 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2414 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2415 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2416 NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
2417 NS_INTERFACE_TABLE_END
2418 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2419 NS_INTERFACE_MAP_END
2421 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2422 NS_IMETHODIMP_(MozExternalRefCountType)
2423 Document::Release() {
2424 MOZ_ASSERT(0 != mRefCnt, "dup release");
2425 NS_ASSERT_OWNINGTHREAD(Document);
2426 nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(Document)::Upcast(this);
2427 bool shouldDelete = false;
2428 nsrefcnt count = mRefCnt.decr(base, &shouldDelete);
2429 NS_LOG_RELEASE(this, count, "Document");
2430 if (count == 0) {
2431 if (mStackRefCnt && !mNeedsReleaseAfterStackRefCntRelease) {
2432 mNeedsReleaseAfterStackRefCntRelease = true;
2433 NS_ADDREF_THIS();
2434 return mRefCnt.get();
2436 mRefCnt.incr(base);
2437 LastRelease();
2438 mRefCnt.decr(base);
2439 if (shouldDelete) {
2440 mRefCnt.stabilizeForDeletion();
2441 DeleteCycleCollectable();
2444 return count;
2447 NS_IMETHODIMP_(void)
2448 Document::DeleteCycleCollectable() { delete this; }
2450 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2451 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2452 EventListenerManager* elm = tmp->GetExistingListenerManager();
2453 if (elm) {
2454 elm->MarkForCC();
2456 return true;
2458 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2460 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2461 return Element::CanSkipInCC(tmp);
2462 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2464 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2465 return Element::CanSkipThis(tmp);
2466 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2468 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2469 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2470 char name[512];
2471 nsAutoCString loadedAsData;
2472 if (tmp->IsLoadedAsData()) {
2473 loadedAsData.AssignLiteral("data");
2474 } else {
2475 loadedAsData.AssignLiteral("normal");
2477 uint32_t nsid = tmp->GetDefaultNamespaceID();
2478 nsAutoCString uri;
2479 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2480 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2481 "(xhtml)", "(XLink)", "(XSLT)",
2482 "(MathML)", "(RDF)", "(XUL)"};
2483 if (nsid < ArrayLength(kNSURIs)) {
2484 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2485 kNSURIs[nsid], uri.get());
2486 } else {
2487 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2489 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2490 } else {
2491 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2494 if (!nsINode::Traverse(tmp, cb)) {
2495 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2498 tmp->mExternalResourceMap.Traverse(&cb);
2500 // Traverse all Document pointer members.
2501 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2502 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2503 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2504 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2505 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2507 // Traverse all Document nsCOMPtrs.
2508 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2509 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2510 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2511 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2512 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2514 DocumentOrShadowRoot::Traverse(tmp, cb);
2516 for (auto& sheets : tmp->mAdditionalSheets) {
2517 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2520 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2521 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver)
2522 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserverViewport)
2523 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2524 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2525 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2526 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2527 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2528 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2529 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
2530 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2533 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2534 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2535 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2536 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2537 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2538 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2539 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2540 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2541 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2542 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2543 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2544 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2545 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2546 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2547 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2549 // Traverse all our nsCOMArrays.
2550 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2552 // Traverse animation components
2553 if (tmp->mAnimationController) {
2554 tmp->mAnimationController->Traverse(&cb);
2557 if (tmp->mSubDocuments) {
2558 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2559 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2561 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2562 cb.NoteXPCOMChild(entry->mKey);
2563 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2564 "mSubDocuments entry->mSubDocument");
2565 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2569 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2571 // We own only the items in mDOMMediaQueryLists that have listeners;
2572 // this reference is managed by their AddListener and RemoveListener
2573 // methods.
2574 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2575 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2576 if (mql->HasListeners() &&
2577 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2578 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2579 cb.NoteXPCOMChild(mql);
2583 // XXX: This should be not needed once bug 1569185 lands.
2584 for (const auto& entry : tmp->mL10nProtoElements) {
2585 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2586 cb.NoteXPCOMChild(entry.GetKey());
2587 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2590 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2591 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2592 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2593 mPendingFrameStaticClones[i].mStaticCloneOf);
2595 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2597 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2599 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document)
2600 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2601 if (tmp->mStateObjectCached.isSome()) {
2602 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStateObjectCached.ref())
2604 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2606 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2607 tmp->mInUnlinkOrDeletion = true;
2609 tmp->SetStateObject(nullptr);
2611 // Clear out our external resources
2612 tmp->mExternalResourceMap.Shutdown();
2614 nsAutoScriptBlocker scriptBlocker;
2616 nsINode::Unlink(tmp);
2618 while (tmp->HasChildren()) {
2619 // Hold a strong ref to the node when we remove it, because we may be
2620 // the last reference to it.
2621 // If this code changes, change the corresponding code in Document's
2622 // unlink impl and ContentUnbinder::UnbindSubtree.
2623 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2624 tmp->DisconnectChild(child);
2625 child->UnbindFromTree();
2628 tmp->UnlinkOriginalDocumentIfStatic();
2630 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2632 tmp->SetScriptGlobalObject(nullptr);
2634 for (auto& sheets : tmp->mAdditionalSheets) {
2635 tmp->UnlinkStyleSheets(sheets);
2638 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2639 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2640 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver)
2641 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserverViewport)
2642 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2643 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2644 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2645 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2646 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2647 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2648 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2649 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2650 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2651 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2652 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2653 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
2654 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2655 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2656 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2657 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2658 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2659 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2660 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2661 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2662 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2663 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2664 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2665 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2666 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2667 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2668 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2669 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2670 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2671 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2673 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2674 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2675 tmp->mDocGroup);
2677 tmp->mDocGroup = nullptr;
2679 if (tmp->IsTopLevelContentDocument()) {
2680 RemoveToplevelLoadingDocument(tmp);
2683 tmp->mParentDocument = nullptr;
2685 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2687 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2689 if (tmp->mListenerManager) {
2690 tmp->mListenerManager->Disconnect();
2691 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2692 tmp->mListenerManager = nullptr;
2695 if (tmp->mStyleSheetSetList) {
2696 tmp->mStyleSheetSetList->Disconnect();
2697 tmp->mStyleSheetSetList = nullptr;
2700 delete tmp->mSubDocuments;
2701 tmp->mSubDocuments = nullptr;
2703 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2704 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2705 "How did we get here without our presshell going away "
2706 "first?");
2708 DocumentOrShadowRoot::Unlink(tmp);
2710 // Document has a pretty complex destructor, so we're going to
2711 // assume that *most* cycles you actually want to break somewhere
2712 // else, and not unlink an awful lot here.
2714 tmp->mExpandoAndGeneration.OwnerUnlinked();
2716 if (tmp->mAnimationController) {
2717 tmp->mAnimationController->Unlink();
2720 tmp->mPendingTitleChangeEvent.Revoke();
2722 if (tmp->mCSSLoader) {
2723 tmp->mCSSLoader->DropDocumentReference();
2724 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2727 // We own only the items in mDOMMediaQueryLists that have listeners;
2728 // this reference is managed by their AddListener and RemoveListener
2729 // methods.
2730 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2731 MediaQueryList* next =
2732 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2733 mql->Disconnect();
2734 mql = next;
2737 tmp->mPendingFrameStaticClones.Clear();
2739 tmp->mInUnlinkOrDeletion = false;
2741 tmp->UnregisterFromMemoryReportingForDataDocument();
2743 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2744 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2745 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2746 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2748 nsresult Document::Init() {
2749 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2750 return NS_ERROR_ALREADY_INITIALIZED;
2753 // Force initialization.
2754 mOnloadBlocker = new OnloadBlocker();
2755 mStyleImageLoader = new css::ImageLoader(this);
2757 mNodeInfoManager = new nsNodeInfoManager();
2758 nsresult rv = mNodeInfoManager->Init(this);
2759 NS_ENSURE_SUCCESS(rv, rv);
2761 // mNodeInfo keeps NodeInfoManager alive!
2762 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2763 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2764 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2765 "Bad NodeType in aNodeInfo");
2767 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2769 mCSSLoader = new css::Loader(this);
2770 // Assume we're not quirky, until we know otherwise
2771 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2773 // If after creation the owner js global is not set for a document
2774 // we use the default compartment for this document, instead of creating
2775 // wrapper in some random compartment when the document is exposed to js
2776 // via some events.
2777 nsCOMPtr<nsIGlobalObject> global =
2778 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2779 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2780 mScopeObject = do_GetWeakReference(global);
2781 MOZ_ASSERT(mScopeObject);
2783 mScriptLoader = new dom::ScriptLoader(this);
2785 // we need to create a policy here so getting the policy within
2786 // ::Policy() can *always* return a non null policy
2787 mFeaturePolicy = new dom::FeaturePolicy(this);
2788 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2790 mStyleSet = MakeUnique<ServoStyleSet>(*this);
2792 return NS_OK;
2795 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2797 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2798 PropertyTable().RemoveAllPropertiesFor(aNode);
2801 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2802 nsCOMPtr<nsIURI> uri;
2803 nsCOMPtr<nsIPrincipal> principal;
2804 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2805 if (aChannel) {
2806 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2807 // nsScriptSecurityManager::GetChannelResultPrincipals.
2808 // Note: this should match the uri used for the OnNewURI call in
2809 // nsDocShell::CreateContentViewer.
2810 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2812 nsIScriptSecurityManager* securityManager =
2813 nsContentUtils::GetSecurityManager();
2814 if (securityManager) {
2815 securityManager->GetChannelResultPrincipals(
2816 aChannel, getter_AddRefs(principal),
2817 getter_AddRefs(partitionedPrincipal));
2821 bool equal = principal->Equals(partitionedPrincipal);
2823 principal = MaybeDowngradePrincipal(principal);
2824 if (equal) {
2825 partitionedPrincipal = principal;
2826 } else {
2827 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2830 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2832 // Note that, since mTiming does not change during a reset, the
2833 // navigationStart time remains unchanged and therefore any future new
2834 // timeline will have the same global clock time as the old one.
2835 mDocumentTimeline = nullptr;
2837 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2838 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2839 mDocumentBaseURI = baseURI.forget();
2840 mChromeXHRDocBaseURI = nullptr;
2844 mChannel = aChannel;
2847 void Document::DisconnectNodeTree() {
2848 // Delete references to sub-documents and kill the subdocument map,
2849 // if any. This is not strictly needed, but makes the node tree
2850 // teardown a bit faster.
2851 delete mSubDocuments;
2852 mSubDocuments = nullptr;
2854 bool oldVal = mInUnlinkOrDeletion;
2855 mInUnlinkOrDeletion = true;
2856 { // Scope for update
2857 MOZ_AUTO_DOC_UPDATE(this, true);
2859 // Destroy link map now so we don't waste time removing
2860 // links one by one
2861 DestroyElementMaps();
2863 // Invalidate cached array of child nodes
2864 InvalidateChildNodes();
2866 while (HasChildren()) {
2867 nsMutationGuard::DidMutate();
2868 nsCOMPtr<nsIContent> content = GetLastChild();
2869 nsIContent* previousSibling = content->GetPreviousSibling();
2870 DisconnectChild(content);
2871 if (content == mCachedRootElement) {
2872 // Immediately clear mCachedRootElement, now that it's been removed
2873 // from mChildren, so that GetRootElement() will stop returning this
2874 // now-stale value.
2875 mCachedRootElement = nullptr;
2877 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2878 content->UnbindFromTree();
2880 MOZ_ASSERT(!mCachedRootElement,
2881 "After removing all children, there should be no root elem");
2883 mInUnlinkOrDeletion = oldVal;
2886 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2887 nsIPrincipal* aPrincipal,
2888 nsIPrincipal* aPartitionedPrincipal) {
2889 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2890 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2892 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2893 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2895 mSecurityInfo = nullptr;
2897 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2898 if (!aLoadGroup || group != aLoadGroup) {
2899 mDocumentLoadGroup = nullptr;
2902 DisconnectNodeTree();
2904 // Reset our stylesheets
2905 ResetStylesheetsToURI(aURI);
2907 // Release the listener manager
2908 if (mListenerManager) {
2909 mListenerManager->Disconnect();
2910 mListenerManager = nullptr;
2913 // Release the stylesheets list.
2914 mDOMStyleSheets = nullptr;
2916 // Release our principal after tearing down the document, rather than before.
2917 // This ensures that, during teardown, the document and the dying window
2918 // (which already nulled out its document pointer and cached the principal)
2919 // have matching principals.
2920 SetPrincipals(nullptr, nullptr);
2922 // Clear the original URI so SetDocumentURI sets it.
2923 mOriginalURI = nullptr;
2925 SetDocumentURI(aURI);
2926 mChromeXHRDocURI = nullptr;
2927 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2928 // mDocumentURI.
2929 mDocumentBaseURI = nullptr;
2930 mChromeXHRDocBaseURI = nullptr;
2932 // Check if the current document is the top-level DevTools document.
2933 // For inner DevTools frames, mIsDevToolsDocument will be set when
2934 // calling SetDocumentParent.
2935 if (aURI && aURI->SchemeIs("about") &&
2936 aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) {
2937 mIsDevToolsDocument = true;
2940 if (aLoadGroup) {
2941 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2942 // there was an assertion here that aLoadGroup was not null. This
2943 // is no longer valid: nsDocShell::SetDocument does not create a
2944 // load group, and it works just fine
2946 // XXXbz what does "just fine" mean exactly? And given that there
2947 // is no nsDocShell::SetDocument, what is this talking about?
2949 if (IsContentDocument()) {
2950 // Inform the associated request context about this load start so
2951 // any of its internal load progress flags gets reset.
2952 nsCOMPtr<nsIRequestContextService> rcsvc =
2953 net::RequestContextService::GetOrCreate();
2954 if (rcsvc) {
2955 nsCOMPtr<nsIRequestContext> rc;
2956 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2957 if (rc) {
2958 rc->BeginLoad();
2964 mLastModified.Truncate();
2965 // XXXbz I guess we're assuming that the caller will either pass in
2966 // a channel with a useful type or call SetContentType?
2967 SetContentType(""_ns);
2968 mContentLanguage.Truncate();
2969 mBaseTarget.Truncate();
2971 mXMLDeclarationBits = 0;
2973 // Now get our new principal
2974 if (aPrincipal) {
2975 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2976 } else {
2977 nsIScriptSecurityManager* securityManager =
2978 nsContentUtils::GetSecurityManager();
2979 if (securityManager) {
2980 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
2982 if (!loadContext && aLoadGroup) {
2983 nsCOMPtr<nsIInterfaceRequestor> cbs;
2984 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
2985 loadContext = do_GetInterface(cbs);
2988 MOZ_ASSERT(loadContext,
2989 "must have a load context or pass in an explicit principal");
2991 nsCOMPtr<nsIPrincipal> principal;
2992 nsresult rv = securityManager->GetLoadContextContentPrincipal(
2993 mDocumentURI, loadContext, getter_AddRefs(principal));
2994 if (NS_SUCCEEDED(rv)) {
2995 SetPrincipals(principal, principal);
3000 if (mFontFaceSet) {
3001 mFontFaceSet->RefreshStandardFontLoadPrincipal();
3004 // Refresh the principal on the realm.
3005 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
3006 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
3010 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
3011 nsIPrincipal* aPrincipal) {
3012 if (!aPrincipal) {
3013 return nullptr;
3016 // We can't load a document with an expanded principal. If we're given one,
3017 // automatically downgrade it to the last principal it subsumes (which is the
3018 // extension principal, in the case of extension content scripts).
3019 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3020 if (basePrin->Is<ExpandedPrincipal>()) {
3021 MOZ_DIAGNOSTIC_ASSERT(false,
3022 "Should never try to create a document with "
3023 "an expanded principal");
3025 auto* expanded = basePrin->As<ExpandedPrincipal>();
3026 return do_AddRef(expanded->AllowList().LastElement());
3029 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
3030 // We basically want the parent document here, but because this is very
3031 // early in the load, GetInProcessParentDocument() returns null, so we use
3032 // the docshell hierarchy to get this information instead.
3033 if (RefPtr<BrowsingContext> parent =
3034 mDocumentContainer->GetBrowsingContext()->GetParent()) {
3035 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
3036 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
3037 nsCOMPtr<nsIPrincipal> nullPrincipal =
3038 NullPrincipal::CreateWithoutOriginAttributes();
3039 return nullPrincipal.forget();
3043 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
3044 return principal.forget();
3047 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
3048 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3050 // lowest index first
3051 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
3053 size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
3054 size_t index = 0;
3055 for (; index < count; index++) {
3056 auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
3057 MOZ_ASSERT(sheet);
3058 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
3059 if (sheetDocIndex > newDocIndex) {
3060 break;
3063 // If the sheet is not owned by the document it can be an author
3064 // sheet registered at nsStyleSheetService or an additional author
3065 // sheet on the document, which means the new
3066 // doc sheet should end up before it.
3067 if (sheetDocIndex < 0) {
3068 if (sheetService) {
3069 auto& authorSheets = *sheetService->AuthorStyleSheets();
3070 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
3071 break;
3074 if (sheet == GetFirstAdditionalAuthorSheet()) {
3075 break;
3080 return index;
3083 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
3084 MOZ_ASSERT(aURI);
3086 ClearAdoptedStyleSheets();
3088 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
3089 for (auto& sheet : Reversed(aSheetList)) {
3090 sheet->ClearAssociatedDocumentOrShadowRoot();
3091 if (mStyleSetFilled) {
3092 mStyleSet->RemoveStyleSheet(*sheet);
3095 aSheetList.Clear();
3097 ClearSheetList(mStyleSheets);
3098 for (auto& sheets : mAdditionalSheets) {
3099 ClearSheetList(sheets);
3101 if (mStyleSetFilled) {
3102 if (auto* ss = nsStyleSheetService::GetInstance()) {
3103 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
3104 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
3105 if (sheet->IsApplicable()) {
3106 mStyleSet->RemoveStyleSheet(*sheet);
3112 // Now reset our inline style and attribute sheets.
3113 if (mAttrStyleSheet) {
3114 mAttrStyleSheet->Reset();
3115 mAttrStyleSheet->SetOwningDocument(this);
3116 } else {
3117 mAttrStyleSheet = new nsHTMLStyleSheet(this);
3120 if (!mStyleAttrStyleSheet) {
3121 mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet();
3124 if (mStyleSetFilled) {
3125 FillStyleSetDocumentSheets();
3127 if (mStyleSet->StyleSheetsHaveChanged()) {
3128 ApplicableStylesChanged();
3133 static void AppendSheetsToStyleSet(
3134 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
3135 for (StyleSheet* sheet : Reversed(aSheets)) {
3136 aStyleSet->AppendStyleSheet(*sheet);
3140 void Document::FillStyleSetUserAndUASheets() {
3141 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
3142 // ordering.
3144 // The document will fill in the document sheets when we create the presshell
3145 auto* cache = GlobalStyleSheetCache::Singleton();
3147 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3148 MOZ_ASSERT(sheetService,
3149 "should never be creating a StyleSet after the style sheet "
3150 "service has gone");
3152 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3153 mStyleSet->AppendStyleSheet(*sheet);
3156 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3157 : cache->GetUserContentSheet();
3158 if (sheet) {
3159 mStyleSet->AppendStyleSheet(*sheet);
3162 mStyleSet->AppendStyleSheet(*cache->UASheet());
3164 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3165 mStyleSet->AppendStyleSheet(*cache->MathMLSheet());
3168 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3169 mStyleSet->AppendStyleSheet(*cache->SVGSheet());
3172 mStyleSet->AppendStyleSheet(*cache->HTMLSheet());
3174 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3175 mStyleSet->AppendStyleSheet(*cache->NoFramesSheet());
3178 if (nsLayoutUtils::ShouldUseNoScriptSheet(this)) {
3179 mStyleSet->AppendStyleSheet(*cache->NoScriptSheet());
3182 mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet());
3184 // Load the minimal XUL rules for scrollbars and a few other XUL things
3185 // that non-XUL (typically HTML) documents commonly use.
3186 mStyleSet->AppendStyleSheet(*cache->MinimalXULSheet());
3188 // Only load the full XUL sheet if we'll need it.
3189 if (LoadsFullXULStyleSheetUpFront()) {
3190 mStyleSet->AppendStyleSheet(*cache->XULSheet());
3193 mStyleSet->AppendStyleSheet(*cache->FormsSheet());
3194 mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet());
3196 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3197 mStyleSet->AppendStyleSheet(*sheet);
3200 MOZ_ASSERT(!mQuirkSheetAdded);
3201 if (NeedsQuirksSheet()) {
3202 mStyleSet->AppendStyleSheet(*cache->QuirkSheet());
3203 mQuirkSheetAdded = true;
3207 void Document::FillStyleSet() {
3208 MOZ_ASSERT(!mStyleSetFilled);
3209 FillStyleSetUserAndUASheets();
3210 FillStyleSetDocumentSheets();
3211 mStyleSetFilled = true;
3214 void Document::RemoveContentEditableStyleSheets() {
3215 MOZ_ASSERT(IsHTMLOrXHTML());
3217 auto* cache = GlobalStyleSheetCache::Singleton();
3218 bool changed = false;
3219 if (mDesignModeSheetAdded) {
3220 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3221 mDesignModeSheetAdded = false;
3222 changed = true;
3224 if (mContentEditableSheetAdded) {
3225 mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet());
3226 mContentEditableSheetAdded = false;
3227 changed = true;
3229 if (changed) {
3230 MOZ_ASSERT(mStyleSetFilled);
3231 ApplicableStylesChanged();
3235 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
3236 MOZ_ASSERT(IsHTMLOrXHTML());
3237 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3238 "Caller should ensure we're being rendered");
3240 auto* cache = GlobalStyleSheetCache::Singleton();
3241 bool changed = false;
3242 if (!mContentEditableSheetAdded) {
3243 mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet());
3244 mContentEditableSheetAdded = true;
3245 changed = true;
3247 if (mDesignModeSheetAdded != aDesignMode) {
3248 if (mDesignModeSheetAdded) {
3249 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3250 } else {
3251 mStyleSet->AppendStyleSheet(*cache->DesignModeSheet());
3253 mDesignModeSheetAdded = !mDesignModeSheetAdded;
3254 changed = true;
3256 if (changed) {
3257 ApplicableStylesChanged();
3261 void Document::FillStyleSetDocumentSheets() {
3262 MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
3263 "Style set already has document sheets?");
3265 // Sheets are added in reverse order to avoid worst-case time complexity when
3266 // looking up the index of a sheet.
3268 // Note that usually appending is faster (rebuilds less stuff in the
3269 // styleset), but in this case it doesn't matter since we're filling the
3270 // styleset from scratch anyway.
3271 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3272 if (sheet->IsApplicable()) {
3273 mStyleSet->AddDocStyleSheet(*sheet);
3277 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3278 if (aSheet.IsApplicable()) {
3279 mStyleSet->AddDocStyleSheet(aSheet);
3283 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3284 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3285 mStyleSet->AppendStyleSheet(*sheet);
3288 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]);
3289 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]);
3290 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]);
3293 void Document::CompatibilityModeChanged() {
3294 MOZ_ASSERT(IsHTMLOrXHTML());
3295 CSSLoader()->SetCompatibilityMode(mCompatMode);
3296 mStyleSet->CompatibilityModeChanged();
3297 if (PresShell* presShell = GetPresShell()) {
3298 // Selectors may have become case-sensitive / case-insensitive, the stylist
3299 // has already performed the relevant invalidation.
3300 presShell->EnsureStyleFlush();
3302 if (!mStyleSetFilled) {
3303 MOZ_ASSERT(!mQuirkSheetAdded);
3304 return;
3306 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3307 return;
3309 auto* cache = GlobalStyleSheetCache::Singleton();
3310 StyleSheet* sheet = cache->QuirkSheet();
3311 if (mQuirkSheetAdded) {
3312 mStyleSet->RemoveStyleSheet(*sheet);
3313 } else {
3314 mStyleSet->AppendStyleSheet(*sheet);
3316 mQuirkSheetAdded = !mQuirkSheetAdded;
3317 ApplicableStylesChanged();
3320 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3321 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3322 "Bad compat mode for XHTML document!");
3324 if (mCompatMode == aMode) {
3325 return;
3327 mCompatMode = aMode;
3328 CompatibilityModeChanged();
3329 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3330 mViewportType = Unknown;
3333 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3334 uint32_t aSandboxFlags,
3335 nsIChannel* aChannel) {
3336 // If the document permits allow-top-navigation and
3337 // allow-top-navigation-by-user-activation this will permit all top
3338 // navigation.
3339 if (aSandboxFlags != SANDBOXED_NONE &&
3340 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3341 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3342 nsContentUtils::ReportToConsole(
3343 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3344 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3345 "BothAllowTopNavigationAndUserActivationPresent");
3347 // If the document is sandboxed (via the HTML5 iframe sandbox
3348 // attribute) and both the allow-scripts and allow-same-origin
3349 // keywords are supplied, the sandboxed document can call into its
3350 // parent document and remove its sandboxing entirely - we print a
3351 // warning to the web console in this case.
3352 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3353 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3354 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3355 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3356 MOZ_ASSERT(bc->IsInProcess());
3358 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3359 if (!parentBC || !parentBC->IsInProcess()) {
3360 // If parent document is not in process, then by construction it
3361 // cannot be same origin.
3362 return;
3365 // Don't warn if our parent is not the top-level document.
3366 if (!parentBC->IsTopContent()) {
3367 return;
3370 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3371 MOZ_ASSERT(parentDocShell);
3373 nsCOMPtr<nsIChannel> parentChannel;
3374 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3375 if (!parentChannel) {
3376 return;
3378 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3379 if (NS_FAILED(rv)) {
3380 return;
3383 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3384 nsCOMPtr<nsIURI> iframeUri;
3385 parentChannel->GetURI(getter_AddRefs(iframeUri));
3386 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3387 "Iframe Sandbox"_ns, parentDocument,
3388 nsContentUtils::eSECURITY_PROPERTIES,
3389 "BothAllowScriptsAndSameOriginPresent",
3390 nsTArray<nsString>(), iframeUri);
3394 bool Document::IsSynthesized() {
3395 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3396 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3399 // static
3400 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3401 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3402 return principal && (principal->IsSystemPrincipal() ||
3403 principal->GetIsAddonOrExpandedAddonPrincipal());
3406 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3407 nsILoadGroup* aLoadGroup,
3408 nsISupports* aContainer,
3409 nsIStreamListener** aDocListener,
3410 bool aReset) {
3411 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3412 nsCOMPtr<nsIURI> uri;
3413 aChannel->GetURI(getter_AddRefs(uri));
3414 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3415 ("DOCUMENT %p StartDocumentLoad %s", this,
3416 uri ? uri->GetSpecOrDefault().get() : ""));
3419 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3420 "Bad readyState");
3421 SetReadyStateInternal(READYSTATE_LOADING);
3423 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3424 mLoadedAsData = true;
3425 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3426 // We need to disable script & style loading in this case.
3427 // We leave them disabled even in EndLoad(), and let anyone
3428 // who puts the document on display to worry about enabling.
3430 // Do not load/process scripts when loading as data
3431 ScriptLoader()->SetEnabled(false);
3433 // styles
3434 CSSLoader()->SetEnabled(
3435 false); // Do not load/process styles when loading as data
3436 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3437 // Allow CSS, but not scripts
3438 ScriptLoader()->SetEnabled(false);
3441 mMayStartLayout = false;
3442 MOZ_ASSERT(!mReadyForIdle,
3443 "We should never hit DOMContentLoaded before this point");
3445 if (aReset) {
3446 Reset(aChannel, aLoadGroup);
3449 nsAutoCString contentType;
3450 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3451 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3452 contentType))) ||
3453 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3454 // XXX this is only necessary for viewsource:
3455 nsACString::const_iterator start, end, semicolon;
3456 contentType.BeginReading(start);
3457 contentType.EndReading(end);
3458 semicolon = start;
3459 FindCharInReadable(';', semicolon, end);
3460 SetContentType(Substring(start, semicolon));
3463 RetrieveRelevantHeaders(aChannel);
3465 mChannel = aChannel;
3466 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3467 if (inStrmChan) {
3468 bool isSrcdocChannel;
3469 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3470 if (isSrcdocChannel) {
3471 mIsSrcdocDocument = true;
3475 if (mChannel) {
3476 nsLoadFlags loadFlags;
3477 mChannel->GetLoadFlags(&loadFlags);
3478 bool isDocument = false;
3479 mChannel->GetIsDocument(&isDocument);
3480 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3481 IsSynthesized() && XRE_IsContentProcess()) {
3482 ContentChild::UpdateCookieStatus(mChannel);
3485 // Store the security info for future use.
3486 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3489 // If this document is being loaded by a docshell, copy its sandbox flags
3490 // to the document, and store the fullscreen enabled flag. These are
3491 // immutable after being set here.
3492 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3494 // If this is an error page, don't inherit sandbox flags
3495 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3496 if (docShell && !loadInfo->GetLoadErrorPage()) {
3497 mSandboxFlags = loadInfo->GetSandboxFlags();
3498 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3501 // Set the opener policy for the top level content document.
3502 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3503 nsILoadInfo::CrossOriginOpenerPolicy policy =
3504 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3505 if (IsTopLevelContentDocument() && httpChan &&
3506 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3507 docShell->GetBrowsingContext()) {
3508 // Setting the opener policy on a discarded context has no effect.
3509 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3512 // The CSP directives upgrade-insecure-requests as well as
3513 // block-all-mixed-content not only apply to the toplevel document,
3514 // but also to nested documents. The loadInfo of a subdocument
3515 // load already holds the correct flag, so let's just set it here
3516 // on the document. Please note that we set the appropriate preload
3517 // bits just for the sake of completeness here, because the preloader
3518 // does not reach into subdocuments.
3519 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3520 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3521 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3522 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3524 // HTTPS-Only Mode flags
3525 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3526 // sub-resources and sub-documents.
3527 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3529 nsresult rv = InitReferrerInfo(aChannel);
3530 NS_ENSURE_SUCCESS(rv, rv);
3532 rv = InitCOEP(aChannel);
3533 NS_ENSURE_SUCCESS(rv, rv);
3535 // Check CSP navigate-to
3536 // We need to enforce the CSP of the document that initiated the load,
3537 // which is the CSP to inherit.
3538 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3539 if (cspToInherit) {
3540 bool allowsNavigateTo = false;
3541 rv = cspToInherit->GetAllowsNavigateTo(
3542 mDocumentURI, loadInfo->GetIsFormSubmission(),
3543 !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
3544 true, /* aEnforceWhitelist */
3545 &allowsNavigateTo);
3546 NS_ENSURE_SUCCESS(rv, rv);
3548 if (!allowsNavigateTo) {
3549 aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
3550 return NS_OK;
3554 rv = InitCSP(aChannel);
3555 NS_ENSURE_SUCCESS(rv, rv);
3557 // Initialize FeaturePolicy
3558 rv = InitFeaturePolicy(aChannel);
3559 NS_ENSURE_SUCCESS(rv, rv);
3561 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3562 NS_ENSURE_SUCCESS(rv, rv);
3564 // Generally XFO and CSP frame-ancestors is handled within
3565 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3566 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3567 nsContentPolicyType internalContentType =
3568 loadInfo->InternalContentPolicyType();
3569 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3570 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3571 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3573 nsresult status;
3574 aChannel->GetStatus(&status);
3575 if (status == NS_ERROR_XFO_VIOLATION) {
3576 // stop! ERROR page!
3577 // But before we have to reset the principal of the document
3578 // because the onload() event fires before the error page
3579 // is displayed and we do not want the enclosing document
3580 // to access the contentDocument.
3581 RefPtr<NullPrincipal> nullPrincipal =
3582 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3583 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3584 // and also GetInnerWindow() is still null at this point, before
3585 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3586 // within Document.cpp
3587 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3588 SetPrincipals(nullPrincipal, nullPrincipal);
3592 return NS_OK;
3595 void Document::SetLoadedAsData(bool aLoadedAsData,
3596 bool aConsiderForMemoryReporting) {
3597 mLoadedAsData = aLoadedAsData;
3598 if (aConsiderForMemoryReporting) {
3599 nsIGlobalObject* global = GetScopeObject();
3600 if (global) {
3601 if (nsPIDOMWindowInner* window = global->AsInnerWindow()) {
3602 nsGlobalWindowInner::Cast(window)
3603 ->RegisterDataDocumentForMemoryReporting(this);
3609 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3611 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3613 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3614 return mPreloadCSP;
3617 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3618 mPreloadCSP = aPreloadCSP;
3621 void Document::GetCspJSON(nsString& aJSON) {
3622 aJSON.Truncate();
3624 if (!mCSP) {
3625 dom::CSPPolicies jsonPolicies;
3626 jsonPolicies.ToJSON(aJSON);
3627 return;
3629 mCSP->ToJSON(aJSON);
3632 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3633 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3634 nsAutoString messageTag;
3635 aMessages[i]->GetTag(messageTag);
3637 nsAutoString category;
3638 aMessages[i]->GetCategory(category);
3640 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3641 NS_ConvertUTF16toUTF8(category), this,
3642 nsContentUtils::eSECURITY_PROPERTIES,
3643 NS_ConvertUTF16toUTF8(messageTag).get());
3647 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3648 nsresult rv = NS_OK;
3649 if (!aSpeculative) {
3650 // 1) apply settings from regular CSP
3651 if (mCSP) {
3652 // Set up 'block-all-mixed-content' if not already inherited
3653 // from the parent context or set by any other CSP.
3654 if (!mBlockAllMixedContent) {
3655 bool block = false;
3656 rv = mCSP->GetBlockAllMixedContent(&block);
3657 NS_ENSURE_SUCCESS_VOID(rv);
3658 mBlockAllMixedContent = block;
3660 if (!mBlockAllMixedContentPreloads) {
3661 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3664 // Set up 'upgrade-insecure-requests' if not already inherited
3665 // from the parent context or set by any other CSP.
3666 if (!mUpgradeInsecureRequests) {
3667 bool upgrade = false;
3668 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3669 NS_ENSURE_SUCCESS_VOID(rv);
3670 mUpgradeInsecureRequests = upgrade;
3672 if (!mUpgradeInsecurePreloads) {
3673 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3675 // Update csp settings in the parent process
3676 if (auto* wgc = GetWindowGlobalChild()) {
3677 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3678 mUpgradeInsecureRequests);
3681 return;
3684 // 2) apply settings from speculative csp
3685 if (mPreloadCSP) {
3686 if (!mBlockAllMixedContentPreloads) {
3687 bool block = false;
3688 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3689 NS_ENSURE_SUCCESS_VOID(rv);
3690 mBlockAllMixedContent = block;
3692 if (!mUpgradeInsecurePreloads) {
3693 bool upgrade = false;
3694 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3695 NS_ENSURE_SUCCESS_VOID(rv);
3696 mUpgradeInsecurePreloads = upgrade;
3701 nsresult Document::InitCSP(nsIChannel* aChannel) {
3702 MOZ_ASSERT(!mScriptGlobalObject,
3703 "CSP must be initialized before mScriptGlobalObject is set!");
3705 // If this is a data document - no need to set CSP.
3706 if (mLoadedAsData) {
3707 return NS_OK;
3710 // If this is an image, no need to set a CSP. Otherwise SVG images
3711 // served with a CSP might block internally applied inline styles.
3712 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3713 if (loadInfo->GetExternalContentPolicyType() ==
3714 ExtContentPolicy::TYPE_IMAGE) {
3715 return NS_OK;
3718 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3720 // If there is a CSP that needs to be inherited from whatever
3721 // global is considered the client of the document fetch then
3722 // we query it here from the loadinfo in case the newly created
3723 // document needs to inherit the CSP. See:
3724 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3725 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3726 if (inheritedCSP) {
3727 mCSP = loadInfo->GetCspToInherit();
3730 // If there is no CSP to inherit, then we create a new CSP here so
3731 // that history entries always have the right reference in case a
3732 // Meta CSP gets dynamically added after the history entry has
3733 // already been created.
3734 if (!mCSP) {
3735 mCSP = new nsCSPContext();
3738 // Always overwrite the requesting context of the CSP so that any new
3739 // 'self' keyword added to an inherited CSP translates correctly.
3740 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3741 if (NS_WARN_IF(NS_FAILED(rv))) {
3742 return rv;
3745 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3747 nsCOMPtr<nsIHttpChannel> httpChannel;
3748 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3749 if (NS_WARN_IF(NS_FAILED(rv))) {
3750 return rv;
3753 if (httpChannel) {
3754 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3755 tCspHeaderValue);
3757 Unused << httpChannel->GetResponseHeader(
3758 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3760 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3761 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3763 // Check if this is a document from a WebExtension.
3764 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3765 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3767 // If there's no CSP to apply, go ahead and return early
3768 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3769 cspROHeaderValue.IsEmpty()) {
3770 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3771 nsCOMPtr<nsIURI> chanURI;
3772 aChannel->GetURI(getter_AddRefs(chanURI));
3773 nsAutoCString aspec;
3774 chanURI->GetAsciiSpec(aspec);
3775 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3776 ("no CSP for document, %s", aspec.get()));
3779 return NS_OK;
3782 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3783 ("Document is an add-on or CSP header specified %p", this));
3785 // ----- if the doc is an addon, apply its CSP.
3786 if (addonPolicy) {
3787 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3789 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3790 // Bug 1548468: Move CSP off ExpandedPrincipal
3791 // Currently the LoadInfo holds the source of truth for every resource load
3792 // because LoadInfo::GetCSP() queries the CSP from an ExpandedPrincipal
3793 // (and not from the Client) if the load was triggered by an extension.
3794 auto* basePrin = BasePrincipal::Cast(principal);
3795 if (basePrin->Is<ExpandedPrincipal>()) {
3796 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3800 // ----- if there's a full-strength CSP header, apply it.
3801 if (!cspHeaderValue.IsEmpty()) {
3802 mHasCSPDeliveredThroughHeader = true;
3803 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3804 NS_ENSURE_SUCCESS(rv, rv);
3807 // ----- if there's a report-only CSP header, apply it.
3808 if (!cspROHeaderValue.IsEmpty()) {
3809 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3810 NS_ENSURE_SUCCESS(rv, rv);
3813 // ----- Enforce sandbox policy if supplied in CSP header
3814 // The document may already have some sandbox flags set (e.g. if the document
3815 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3816 // directive, intersect the CSP sandbox flags with the existing flags. This
3817 // corresponds to the _least_ permissive policy.
3818 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3819 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3820 NS_ENSURE_SUCCESS(rv, rv);
3822 // Probably the iframe sandbox attribute already caused the creation of a
3823 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3824 // and no one has been created yet.
3825 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3826 !(mSandboxFlags & SANDBOXED_ORIGIN);
3828 mSandboxFlags |= cspSandboxFlags;
3830 if (needNewNullPrincipal) {
3831 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3832 // Skip setting the content blocking allowlist principal to NullPrincipal.
3833 // The principal is only used to enable/disable trackingprotection via
3834 // permission and can be shared with the top level sandboxed site.
3835 // See Bug 1654546.
3836 SetPrincipals(principal, principal);
3839 ApplySettingsFromCSP(false);
3840 return NS_OK;
3843 static Document* GetInProcessParentDocumentFrom(BrowsingContext* aContext) {
3844 BrowsingContext* parentContext = aContext->GetParent();
3845 if (!parentContext) {
3846 return nullptr;
3849 WindowContext* windowContext = parentContext->GetCurrentWindowContext();
3850 if (!windowContext) {
3851 return nullptr;
3854 return windowContext->GetDocument();
3857 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3858 BrowsingContext* browsingContext = GetBrowsingContext();
3859 if (!browsingContext) {
3860 return nullptr;
3862 if (!browsingContext->IsContentSubframe()) {
3863 return nullptr;
3866 HTMLIFrameElement* iframe =
3867 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3868 if (iframe) {
3869 return do_AddRef(iframe->FeaturePolicy());
3872 if (XRE_IsParentProcess()) {
3873 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3876 if (Document* parentDocument =
3877 GetInProcessParentDocumentFrom(browsingContext)) {
3878 return do_AddRef(parentDocument->FeaturePolicy());
3881 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3882 if (!windowContext) {
3883 return nullptr;
3886 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3887 if (!child) {
3888 return nullptr;
3891 return do_AddRef(child->GetContainerFeaturePolicy());
3894 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3895 MOZ_ASSERT(mFeaturePolicy, "we should only call init once");
3897 mFeaturePolicy->ResetDeclaredPolicy();
3899 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3901 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3902 if (parentPolicy) {
3903 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3904 mFeaturePolicy->InheritPolicy(parentPolicy);
3905 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3908 // We don't want to parse the http Feature-Policy header if this pref is off.
3909 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3910 return NS_OK;
3913 nsCOMPtr<nsIHttpChannel> httpChannel;
3914 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3915 if (NS_WARN_IF(NS_FAILED(rv))) {
3916 return rv;
3919 if (!httpChannel) {
3920 return NS_OK;
3923 // query the policy from the header
3924 nsAutoCString value;
3925 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3926 if (NS_SUCCEEDED(rv)) {
3927 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3928 NodePrincipal(), nullptr);
3931 return NS_OK;
3934 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3935 MOZ_ASSERT(mReferrerInfo);
3936 MOZ_ASSERT(mPreloadReferrerInfo);
3938 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3939 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
3940 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
3941 // they have an opaque origin.
3942 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
3943 if (BrowsingContext* bc = GetBrowsingContext()) {
3944 // At this point the document is not fully created and mParentDocument has
3945 // not been set yet,
3946 Document* parentDoc = bc->GetEmbedderElement()
3947 ? bc->GetEmbedderElement()->OwnerDoc()
3948 : nullptr;
3949 if (parentDoc) {
3950 mReferrerInfo = parentDoc->GetReferrerInfo();
3951 mPreloadReferrerInfo = mReferrerInfo;
3952 return NS_OK;
3955 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
3956 "srcdoc without null principal as toplevel!");
3960 nsCOMPtr<nsIHttpChannel> httpChannel;
3961 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3962 if (NS_WARN_IF(NS_FAILED(rv))) {
3963 return rv;
3966 if (!httpChannel) {
3967 return NS_OK;
3970 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
3971 if (referrerInfo) {
3972 mReferrerInfo = referrerInfo;
3975 // Override policy if we get one from Referrerr-Policy header
3976 mozilla::dom::ReferrerPolicy policy =
3977 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
3978 mReferrerInfo = static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
3979 ->CloneWithNewPolicy(policy);
3981 mPreloadReferrerInfo = mReferrerInfo;
3982 return NS_OK;
3985 nsresult Document::InitCOEP(nsIChannel* aChannel) {
3986 nsCOMPtr<nsIHttpChannel> httpChannel;
3987 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3988 if (NS_FAILED(rv)) {
3989 return NS_OK;
3992 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
3994 if (!intChannel) {
3995 return NS_OK;
3998 nsILoadInfo::CrossOriginEmbedderPolicy policy =
3999 nsILoadInfo::EMBEDDER_POLICY_NULL;
4000 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(&policy))) {
4001 mEmbedderPolicy = Some(policy);
4004 return NS_OK;
4007 void Document::StopDocumentLoad() {
4008 if (mParser) {
4009 mParserAborted = true;
4010 mParser->Terminate();
4014 void Document::SetDocumentURI(nsIURI* aURI) {
4015 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
4016 mDocumentURI = aURI;
4017 nsIURI* newBase = GetDocBaseURI();
4019 mDocURISchemeIsChrome = aURI && IsChromeURI(aURI);
4021 bool equalBases = false;
4022 // Changing just the ref of a URI does not change how relative URIs would
4023 // resolve wrt to it, so we can treat the bases as equal as long as they're
4024 // equal ignoring the ref.
4025 if (oldBase && newBase) {
4026 oldBase->EqualsExceptRef(newBase, &equalBases);
4027 } else {
4028 equalBases = !oldBase && !newBase;
4031 // If this is the first time we're setting the document's URI, set the
4032 // document's original URI.
4033 if (!mOriginalURI) mOriginalURI = mDocumentURI;
4035 // If changing the document's URI changed the base URI of the document, we
4036 // need to refresh the hrefs of all the links on the page.
4037 if (!equalBases) {
4038 RefreshLinkHrefs();
4041 // Recalculate our base domain
4042 mBaseDomain.Truncate();
4043 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
4044 if (thirdPartyUtil) {
4045 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
4048 // Tell our WindowGlobalParent that the document's URI has been changed.
4049 nsPIDOMWindowInner* inner = GetInnerWindow();
4050 if (inner && inner->GetWindowGlobalChild()) {
4051 inner->GetWindowGlobalChild()->SetDocumentURI(mDocumentURI);
4055 static void GetFormattedTimeString(PRTime aTime,
4056 nsAString& aFormattedTimeString) {
4057 PRExplodedTime prtime;
4058 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
4059 // "MM/DD/YYYY hh:mm:ss"
4060 char formatedTime[24];
4061 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
4062 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
4063 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
4064 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
4065 } else {
4066 // If we for whatever reason failed to find the last modified time
4067 // (or even the current time), fall back to what NS4.x returned.
4068 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
4072 void Document::GetLastModified(nsAString& aLastModified) const {
4073 if (!mLastModified.IsEmpty()) {
4074 aLastModified.Assign(mLastModified);
4075 } else {
4076 GetFormattedTimeString(PR_Now(), aLastModified);
4080 static void IncrementExpandoGeneration(Document& aDoc) {
4081 ++aDoc.mExpandoAndGeneration.generation;
4084 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
4085 MOZ_ASSERT(
4086 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
4087 "Only put elements that need to be exposed as document['name'] in "
4088 "the named table.");
4090 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
4092 // Null for out-of-memory
4093 if (entry) {
4094 if (!entry->HasNameElement() &&
4095 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4096 IncrementExpandoGeneration(*this);
4098 entry->AddNameElement(this, aElement);
4102 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
4103 // Speed up document teardown
4104 if (mIdentifierMap.Count() == 0) return;
4106 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
4107 if (!entry) // Could be false if the element was anonymous, hence never added
4108 return;
4110 entry->RemoveNameElement(aElement);
4111 if (!entry->HasNameElement() &&
4112 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4113 IncrementExpandoGeneration(*this);
4117 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4118 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4120 if (entry) { /* True except on OOM */
4121 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4122 !entry->HasNameElement() &&
4123 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4124 IncrementExpandoGeneration(*this);
4126 entry->AddIdElement(aElement);
4130 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4131 NS_ASSERTION(aId, "huhwhatnow?");
4133 // Speed up document teardown
4134 if (mIdentifierMap.Count() == 0) {
4135 return;
4138 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4139 if (!entry) // Can be null for XML elements with changing ids.
4140 return;
4142 entry->RemoveIdElement(aElement);
4143 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4144 !entry->HasNameElement() &&
4145 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4146 IncrementExpandoGeneration(*this);
4148 if (entry->IsEmpty()) {
4149 mIdentifierMap.RemoveEntry(entry);
4153 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4154 bool aPreload) {
4155 ReferrerPolicyEnum policy =
4156 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4157 // The empty string "" corresponds to no referrer policy, causing a fallback
4158 // to a referrer policy defined elsewhere.
4159 if (policy == ReferrerPolicy::_empty) {
4160 return;
4163 MOZ_ASSERT(mReferrerInfo);
4164 MOZ_ASSERT(mPreloadReferrerInfo);
4166 if (aPreload) {
4167 mPreloadReferrerInfo =
4168 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4169 ->CloneWithNewPolicy(policy);
4170 } else {
4171 mReferrerInfo =
4172 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4173 ->CloneWithNewPolicy(policy);
4177 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4178 nsIPrincipal* aNewPartitionedPrincipal) {
4179 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4180 if (aNewPrincipal && mAllowDNSPrefetch &&
4181 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4182 if (aNewPrincipal->SchemeIs("https")) {
4183 mAllowDNSPrefetch = false;
4187 mCSSLoader->DeregisterFromSheetCache();
4189 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4190 mPartitionedPrincipal = aNewPartitionedPrincipal;
4192 mCSSLoader->RegisterInSheetCache();
4194 #ifdef DEBUG
4195 // Validate that the docgroup is set correctly by calling its getter and
4196 // triggering its sanity check.
4198 // If we're setting the principal to null, we don't want to perform the check,
4199 // as the document is entering an intermediate state where it does not have a
4200 // principal. It will be given another real principal shortly which we will
4201 // check. It's not unsafe to have a document which has a null principal in the
4202 // same docgroup as another document, so this should not be a problem.
4203 if (aNewPrincipal) {
4204 GetDocGroup();
4206 #endif
4209 #ifdef DEBUG
4210 void Document::AssertDocGroupMatchesKey() const {
4211 // Sanity check that we have an up-to-date and accurate docgroup
4212 // We only check if the principal when we can get the browsing context.
4213 if (!GetBrowsingContext()) {
4214 return;
4217 if (mDocGroup) {
4218 nsAutoCString docGroupKey;
4220 // GetKey() can fail, e.g. after the TLD service has shut down.
4221 nsresult rv = mozilla::dom::DocGroup::GetKey(
4222 NodePrincipal(), CrossOriginIsolated(), docGroupKey);
4223 if (NS_SUCCEEDED(rv)) {
4224 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4228 #endif
4230 nsresult Document::Dispatch(TaskCategory aCategory,
4231 already_AddRefed<nsIRunnable>&& aRunnable) {
4232 // Note that this method may be called off the main thread.
4233 if (mDocGroup) {
4234 return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
4236 return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
4239 nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
4240 if (mDocGroup) {
4241 return mDocGroup->EventTargetFor(aCategory);
4243 return DispatcherTrait::EventTargetFor(aCategory);
4246 AbstractThread* Document::AbstractMainThreadFor(
4247 mozilla::TaskCategory aCategory) {
4248 MOZ_ASSERT(NS_IsMainThread());
4249 if (mDocGroup) {
4250 return mDocGroup->AbstractMainThreadFor(aCategory);
4252 return DispatcherTrait::AbstractMainThreadFor(aCategory);
4255 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4256 bool aIsTracking) {
4257 if (aIsTracking) {
4258 mTrackingScripts.Insert(aURL);
4259 } else {
4260 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4264 bool Document::IsScriptTracking(JSContext* aCx) const {
4265 JS::AutoFilename filename;
4266 uint32_t line = 0;
4267 uint32_t column = 0;
4268 if (!JS::DescribeScriptedCaller(aCx, &filename, &line, &column)) {
4269 return false;
4271 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4274 void Document::GetContentType(nsAString& aContentType) {
4275 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4278 void Document::SetContentType(const nsACString& aContentType) {
4279 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4280 aContentType.EqualsLiteral("application/xhtml+xml")) {
4281 mDefaultElementType = kNameSpaceID_XHTML;
4284 mCachedEncoder = nullptr;
4285 mContentType = aContentType;
4288 bool Document::GetAllowPlugins() {
4289 // First, we ask our docshell if it allows plugins.
4290 auto* browsingContext = GetBrowsingContext();
4292 if (browsingContext) {
4293 if (!browsingContext->GetAllowPlugins()) {
4294 return false;
4297 // If the docshell allows plugins, we check whether
4298 // we are sandboxed and plugins should not be allowed.
4299 if (mSandboxFlags & SANDBOXED_PLUGINS) {
4300 return false;
4304 return true;
4307 bool Document::HasPendingInitialTranslation() {
4308 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4311 DocumentL10n* Document::GetL10n() { return mDocumentL10n; }
4313 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4314 JS::Rooted<JSObject*> object(aCx, aObject);
4315 nsCOMPtr<nsIPrincipal> callerPrincipal =
4316 nsContentUtils::SubjectPrincipal(aCx);
4317 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4318 bool allowed = false;
4319 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4320 &allowed);
4321 return allowed;
4324 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4325 if (!AllowsL10n()) {
4326 return;
4329 nsAutoString href;
4330 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
4332 if (!mDocumentL10n) {
4333 Element* elem = GetDocumentElement();
4334 MOZ_DIAGNOSTIC_ASSERT(elem);
4336 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4337 mDocumentL10n = DocumentL10n::Create(this, isSync);
4338 if (NS_WARN_IF(!mDocumentL10n)) {
4339 return;
4343 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4345 if (mReadyState >= READYSTATE_INTERACTIVE) {
4346 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4347 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4348 &DocumentL10n::TriggerInitialTranslation));
4349 } else {
4350 if (!mDocumentL10n->mBlockingLayout) {
4351 // Our initial translation is going to block layout start. Make sure
4352 // we don't fire the load event until after that stops happening and
4353 // layout has a chance to start.
4354 BlockOnload();
4355 mDocumentL10n->mBlockingLayout = true;
4360 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4361 if (!AllowsL10n()) {
4362 return;
4365 if (mDocumentL10n) {
4366 nsAutoString href;
4367 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
4368 uint32_t remaining =
4369 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4370 if (remaining == 0) {
4371 if (mDocumentL10n->mBlockingLayout) {
4372 mDocumentL10n->mBlockingLayout = false;
4373 UnblockOnload(/* aFireSync = */ false);
4375 mDocumentL10n = nullptr;
4381 * This method should be called once the end of the l10n
4382 * resource container has been parsed.
4384 * In XUL this is the end of the first </linkset>,
4385 * In XHTML/HTML this is the end of </head>.
4387 * This milestone is used to allow for batch
4388 * localization context I/O and building done
4389 * once when all resources in the document have been
4390 * collected.
4392 void Document::OnL10nResourceContainerParsed() {
4393 // XXX: This is a scaffolding for where we might inject prefetch
4394 // in bug 1717241.
4397 void Document::OnParsingCompleted() {
4398 // Let's call it again, in case the resource
4399 // container has not been closed, and only
4400 // now we're closing the document.
4401 OnL10nResourceContainerParsed();
4403 if (mDocumentL10n) {
4404 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4405 l10n->TriggerInitialTranslation();
4409 void Document::InitialTranslationCompleted(bool aL10nCached) {
4410 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4411 // This means we blocked the load event in LocalizationLinkAdded. It's
4412 // important that the load blocker removal here be async, because our caller
4413 // will notify the content sink after us, and we want the content sync's
4414 // work to happen before the load event fires.
4415 mDocumentL10n->mBlockingLayout = false;
4416 UnblockOnload(/* aFireSync = */ false);
4419 mL10nProtoElements.Clear();
4421 nsXULPrototypeDocument* proto = GetPrototype();
4422 if (proto) {
4423 proto->SetIsL10nCached(aL10nCached);
4427 bool Document::AllowsL10n() const {
4428 if (IsStaticDocument()) {
4429 // We don't allow l10n on static documents, because the nodes are already
4430 // cloned translated, and static docs don't get parsed so we never
4431 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4432 // forever.
4433 return false;
4435 bool allowed = false;
4436 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4437 return allowed;
4440 bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
4441 MOZ_ASSERT(NS_IsMainThread());
4443 return nsContentUtils::IsSystemCaller(aCx) ||
4444 StaticPrefs::dom_animations_api_core_enabled();
4447 bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
4448 MOZ_ASSERT(NS_IsMainThread());
4450 return aCallerType == dom::CallerType::System ||
4451 StaticPrefs::dom_animations_api_core_enabled();
4454 bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
4455 JSObject* /*unused*/
4457 MOZ_ASSERT(NS_IsMainThread());
4459 return nsContentUtils::IsSystemCaller(aCx) ||
4460 StaticPrefs::dom_animations_api_getAnimations_enabled();
4463 bool Document::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
4464 JSObject* /*unused*/
4466 MOZ_ASSERT(NS_IsMainThread());
4468 return nsContentUtils::IsSystemCaller(aCx) ||
4469 StaticPrefs::dom_animations_api_implicit_keyframes_enabled();
4472 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4473 JSObject* /*unused*/
4475 MOZ_ASSERT(NS_IsMainThread());
4477 return nsContentUtils::IsSystemCaller(aCx) ||
4478 StaticPrefs::dom_animations_api_timelines_enabled();
4481 DocumentTimeline* Document::Timeline() {
4482 if (!mDocumentTimeline) {
4483 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4486 return mDocumentTimeline;
4489 SVGSVGElement* Document::GetSVGRootElement() const {
4490 Element* root = GetRootElement();
4491 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4492 return nullptr;
4494 return static_cast<SVGSVGElement*>(root);
4497 /* Return true if the document is in the focused top-level window, and is an
4498 * ancestor of the focused DOMWindow. */
4499 bool Document::HasFocus(ErrorResult& rv) const {
4500 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4501 if (!fm) {
4502 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4503 return false;
4506 BrowsingContext* bc = GetBrowsingContext();
4507 if (!bc) {
4508 return false;
4511 if (!fm->IsInActiveWindow(bc)) {
4512 return false;
4515 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4518 void Document::GetDesignMode(nsAString& aDesignMode) {
4519 if (IsInDesignMode()) {
4520 aDesignMode.AssignLiteral("on");
4521 } else {
4522 aDesignMode.AssignLiteral("off");
4526 void Document::SetDesignMode(const nsAString& aDesignMode,
4527 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4528 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4531 static void NotifyEditableStateChange(Document& aDoc) {
4532 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4533 nsMutationGuard g;
4534 #endif
4535 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4536 node = node->GetNextNode(&aDoc)) {
4537 if (auto* element = Element::FromNode(node)) {
4538 element->UpdateState(true);
4541 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4544 void Document::SetDesignMode(const nsAString& aDesignMode,
4545 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4546 ErrorResult& rv) {
4547 if (aSubjectPrincipal.isSome() &&
4548 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4549 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4550 return;
4552 const bool editableMode = IsInDesignMode();
4553 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4554 SetEditableFlag(!editableMode);
4555 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4556 // state of all descendant elements of it. Update that now.
4557 NotifyEditableStateChange(*this);
4558 rv = EditingStateChanged();
4562 nsCommandManager* Document::GetMidasCommandManager() {
4563 // check if we have it cached
4564 if (mMidasCommandManager) {
4565 return mMidasCommandManager;
4568 nsPIDOMWindowOuter* window = GetWindow();
4569 if (!window) {
4570 return nullptr;
4573 nsIDocShell* docshell = window->GetDocShell();
4574 if (!docshell) {
4575 return nullptr;
4578 mMidasCommandManager = docshell->GetCommandManager();
4579 return mMidasCommandManager;
4582 // static
4583 void Document::EnsureInitializeInternalCommandDataHashtable() {
4584 if (sInternalCommandDataHashtable) {
4585 return;
4587 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4588 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4589 // clang-format off
4590 sInternalCommandDataHashtable->InsertOrUpdate(
4591 u"bold"_ns,
4592 InternalCommandData(
4593 "cmd_bold",
4594 Command::FormatBold,
4595 ExecCommandParam::Ignore,
4596 StyleUpdatingCommand::GetInstance,
4597 CommandOnTextEditor::Disabled));
4598 sInternalCommandDataHashtable->InsertOrUpdate(
4599 u"italic"_ns,
4600 InternalCommandData(
4601 "cmd_italic",
4602 Command::FormatItalic,
4603 ExecCommandParam::Ignore,
4604 StyleUpdatingCommand::GetInstance,
4605 CommandOnTextEditor::Disabled));
4606 sInternalCommandDataHashtable->InsertOrUpdate(
4607 u"underline"_ns,
4608 InternalCommandData(
4609 "cmd_underline",
4610 Command::FormatUnderline,
4611 ExecCommandParam::Ignore,
4612 StyleUpdatingCommand::GetInstance,
4613 CommandOnTextEditor::Disabled));
4614 sInternalCommandDataHashtable->InsertOrUpdate(
4615 u"strikethrough"_ns,
4616 InternalCommandData(
4617 "cmd_strikethrough",
4618 Command::FormatStrikeThrough,
4619 ExecCommandParam::Ignore,
4620 StyleUpdatingCommand::GetInstance,
4621 CommandOnTextEditor::Disabled));
4622 sInternalCommandDataHashtable->InsertOrUpdate(
4623 u"subscript"_ns,
4624 InternalCommandData(
4625 "cmd_subscript",
4626 Command::FormatSubscript,
4627 ExecCommandParam::Ignore,
4628 StyleUpdatingCommand::GetInstance,
4629 CommandOnTextEditor::Disabled));
4630 sInternalCommandDataHashtable->InsertOrUpdate(
4631 u"superscript"_ns,
4632 InternalCommandData(
4633 "cmd_superscript",
4634 Command::FormatSuperscript,
4635 ExecCommandParam::Ignore,
4636 StyleUpdatingCommand::GetInstance,
4637 CommandOnTextEditor::Disabled));
4638 sInternalCommandDataHashtable->InsertOrUpdate(
4639 u"cut"_ns,
4640 InternalCommandData(
4641 "cmd_cut",
4642 Command::Cut,
4643 ExecCommandParam::Ignore,
4644 CutCommand::GetInstance,
4645 CommandOnTextEditor::Enabled));
4646 sInternalCommandDataHashtable->InsertOrUpdate(
4647 u"copy"_ns,
4648 InternalCommandData(
4649 "cmd_copy",
4650 Command::Copy,
4651 ExecCommandParam::Ignore,
4652 CopyCommand::GetInstance,
4653 CommandOnTextEditor::Enabled));
4654 sInternalCommandDataHashtable->InsertOrUpdate(
4655 u"paste"_ns,
4656 InternalCommandData(
4657 "cmd_paste",
4658 Command::Paste,
4659 ExecCommandParam::Ignore,
4660 PasteCommand::GetInstance,
4661 CommandOnTextEditor::Enabled));
4662 sInternalCommandDataHashtable->InsertOrUpdate(
4663 u"delete"_ns,
4664 InternalCommandData(
4665 "cmd_deleteCharBackward",
4666 Command::DeleteCharBackward,
4667 ExecCommandParam::Ignore,
4668 DeleteCommand::GetInstance,
4669 CommandOnTextEditor::Enabled));
4670 sInternalCommandDataHashtable->InsertOrUpdate(
4671 u"forwarddelete"_ns,
4672 InternalCommandData(
4673 "cmd_deleteCharForward",
4674 Command::DeleteCharForward,
4675 ExecCommandParam::Ignore,
4676 DeleteCommand::GetInstance,
4677 CommandOnTextEditor::Enabled));
4678 sInternalCommandDataHashtable->InsertOrUpdate(
4679 u"selectall"_ns,
4680 InternalCommandData(
4681 "cmd_selectAll",
4682 Command::SelectAll,
4683 ExecCommandParam::Ignore,
4684 SelectAllCommand::GetInstance,
4685 CommandOnTextEditor::Enabled));
4686 sInternalCommandDataHashtable->InsertOrUpdate(
4687 u"undo"_ns,
4688 InternalCommandData(
4689 "cmd_undo",
4690 Command::HistoryUndo,
4691 ExecCommandParam::Ignore,
4692 UndoCommand::GetInstance,
4693 CommandOnTextEditor::Enabled));
4694 sInternalCommandDataHashtable->InsertOrUpdate(
4695 u"redo"_ns,
4696 InternalCommandData(
4697 "cmd_redo",
4698 Command::HistoryRedo,
4699 ExecCommandParam::Ignore,
4700 RedoCommand::GetInstance,
4701 CommandOnTextEditor::Enabled));
4702 sInternalCommandDataHashtable->InsertOrUpdate(
4703 u"indent"_ns,
4704 InternalCommandData("cmd_indent",
4705 Command::FormatIndent,
4706 ExecCommandParam::Ignore,
4707 IndentCommand::GetInstance,
4708 CommandOnTextEditor::Disabled));
4709 sInternalCommandDataHashtable->InsertOrUpdate(
4710 u"outdent"_ns,
4711 InternalCommandData(
4712 "cmd_outdent",
4713 Command::FormatOutdent,
4714 ExecCommandParam::Ignore,
4715 OutdentCommand::GetInstance,
4716 CommandOnTextEditor::Disabled));
4717 sInternalCommandDataHashtable->InsertOrUpdate(
4718 u"backcolor"_ns,
4719 InternalCommandData(
4720 "cmd_highlight",
4721 Command::FormatBackColor,
4722 ExecCommandParam::String,
4723 HighlightColorStateCommand::GetInstance,
4724 CommandOnTextEditor::Disabled));
4725 sInternalCommandDataHashtable->InsertOrUpdate(
4726 u"hilitecolor"_ns,
4727 InternalCommandData(
4728 "cmd_highlight",
4729 Command::FormatBackColor,
4730 ExecCommandParam::String,
4731 HighlightColorStateCommand::GetInstance,
4732 CommandOnTextEditor::Disabled));
4733 sInternalCommandDataHashtable->InsertOrUpdate(
4734 u"forecolor"_ns,
4735 InternalCommandData(
4736 "cmd_fontColor",
4737 Command::FormatFontColor,
4738 ExecCommandParam::String,
4739 FontColorStateCommand::GetInstance,
4740 CommandOnTextEditor::Disabled));
4741 sInternalCommandDataHashtable->InsertOrUpdate(
4742 u"fontname"_ns,
4743 InternalCommandData(
4744 "cmd_fontFace",
4745 Command::FormatFontName,
4746 ExecCommandParam::String,
4747 FontFaceStateCommand::GetInstance,
4748 CommandOnTextEditor::Disabled));
4749 sInternalCommandDataHashtable->InsertOrUpdate(
4750 u"fontsize"_ns,
4751 InternalCommandData(
4752 "cmd_fontSize",
4753 Command::FormatFontSize,
4754 ExecCommandParam::String,
4755 FontSizeStateCommand::GetInstance,
4756 CommandOnTextEditor::Disabled));
4757 sInternalCommandDataHashtable->InsertOrUpdate(
4758 u"increasefontsize"_ns,
4759 InternalCommandData(
4760 "cmd_increaseFont",
4761 Command::FormatIncreaseFontSize,
4762 ExecCommandParam::Ignore,
4763 IncreaseFontSizeCommand::GetInstance,
4764 CommandOnTextEditor::Disabled));
4765 sInternalCommandDataHashtable->InsertOrUpdate(
4766 u"decreasefontsize"_ns,
4767 InternalCommandData(
4768 "cmd_decreaseFont",
4769 Command::FormatDecreaseFontSize,
4770 ExecCommandParam::Ignore,
4771 DecreaseFontSizeCommand::GetInstance,
4772 CommandOnTextEditor::Disabled));
4773 sInternalCommandDataHashtable->InsertOrUpdate(
4774 u"inserthorizontalrule"_ns,
4775 InternalCommandData(
4776 "cmd_insertHR",
4777 Command::InsertHorizontalRule,
4778 ExecCommandParam::Ignore,
4779 InsertTagCommand::GetInstance,
4780 CommandOnTextEditor::Disabled));
4781 sInternalCommandDataHashtable->InsertOrUpdate(
4782 u"createlink"_ns,
4783 InternalCommandData(
4784 "cmd_insertLinkNoUI",
4785 Command::InsertLink,
4786 ExecCommandParam::String,
4787 InsertTagCommand::GetInstance,
4788 CommandOnTextEditor::Disabled));
4789 sInternalCommandDataHashtable->InsertOrUpdate(
4790 u"insertimage"_ns,
4791 InternalCommandData(
4792 "cmd_insertImageNoUI",
4793 Command::InsertImage,
4794 ExecCommandParam::String,
4795 InsertTagCommand::GetInstance,
4796 CommandOnTextEditor::Disabled));
4797 sInternalCommandDataHashtable->InsertOrUpdate(
4798 u"inserthtml"_ns,
4799 InternalCommandData(
4800 "cmd_insertHTML",
4801 Command::InsertHTML,
4802 ExecCommandParam::String,
4803 InsertHTMLCommand::GetInstance,
4804 // TODO: Chromium inserts text content of the document fragment
4805 // created from the param.
4806 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4807 CommandOnTextEditor::Disabled));
4808 sInternalCommandDataHashtable->InsertOrUpdate(
4809 u"inserttext"_ns,
4810 InternalCommandData(
4811 "cmd_insertText",
4812 Command::InsertText,
4813 ExecCommandParam::String,
4814 InsertPlaintextCommand::GetInstance,
4815 CommandOnTextEditor::Enabled));
4816 sInternalCommandDataHashtable->InsertOrUpdate(
4817 u"gethtml"_ns,
4818 InternalCommandData(
4819 "cmd_getContents",
4820 Command::GetHTML,
4821 ExecCommandParam::Ignore,
4822 nullptr, // Not defined in EditorCommands.h
4823 // getHTML command is not supported by Chromium, and we return HTML
4824 // source code at selected range. So, let's return selected text
4825 // when `<input>` or `<textarea>` has focus.
4826 CommandOnTextEditor::Enabled));
4827 sInternalCommandDataHashtable->InsertOrUpdate(
4828 u"justifyleft"_ns,
4829 InternalCommandData(
4830 "cmd_align",
4831 Command::FormatJustifyLeft,
4832 ExecCommandParam::Ignore, // Will be set to "left"
4833 AlignCommand::GetInstance,
4834 CommandOnTextEditor::Disabled));
4835 sInternalCommandDataHashtable->InsertOrUpdate(
4836 u"justifyright"_ns,
4837 InternalCommandData(
4838 "cmd_align",
4839 Command::FormatJustifyRight,
4840 ExecCommandParam::Ignore, // Will be set to "right"
4841 AlignCommand::GetInstance,
4842 CommandOnTextEditor::Disabled));
4843 sInternalCommandDataHashtable->InsertOrUpdate(
4844 u"justifycenter"_ns,
4845 InternalCommandData(
4846 "cmd_align",
4847 Command::FormatJustifyCenter,
4848 ExecCommandParam::Ignore, // Will be set to "center"
4849 AlignCommand::GetInstance,
4850 CommandOnTextEditor::Disabled));
4851 sInternalCommandDataHashtable->InsertOrUpdate(
4852 u"justifyfull"_ns,
4853 InternalCommandData(
4854 "cmd_align",
4855 Command::FormatJustifyFull,
4856 ExecCommandParam::Ignore, // Will be set to "justify"
4857 AlignCommand::GetInstance,
4858 CommandOnTextEditor::Disabled));
4859 sInternalCommandDataHashtable->InsertOrUpdate(
4860 u"removeformat"_ns,
4861 InternalCommandData(
4862 "cmd_removeStyles",
4863 Command::FormatRemove,
4864 ExecCommandParam::Ignore,
4865 RemoveStylesCommand::GetInstance,
4866 CommandOnTextEditor::Disabled));
4867 sInternalCommandDataHashtable->InsertOrUpdate(
4868 u"unlink"_ns,
4869 InternalCommandData(
4870 "cmd_removeLinks",
4871 Command::FormatRemoveLink,
4872 ExecCommandParam::Ignore,
4873 StyleUpdatingCommand::GetInstance,
4874 CommandOnTextEditor::Disabled));
4875 sInternalCommandDataHashtable->InsertOrUpdate(
4876 u"insertorderedlist"_ns,
4877 InternalCommandData(
4878 "cmd_ol",
4879 Command::InsertOrderedList,
4880 ExecCommandParam::Ignore,
4881 ListCommand::GetInstance,
4882 CommandOnTextEditor::Disabled));
4883 sInternalCommandDataHashtable->InsertOrUpdate(
4884 u"insertunorderedlist"_ns,
4885 InternalCommandData(
4886 "cmd_ul",
4887 Command::InsertUnorderedList,
4888 ExecCommandParam::Ignore,
4889 ListCommand::GetInstance,
4890 CommandOnTextEditor::Disabled));
4891 sInternalCommandDataHashtable->InsertOrUpdate(
4892 u"insertparagraph"_ns,
4893 InternalCommandData(
4894 "cmd_insertParagraph",
4895 Command::InsertParagraph,
4896 ExecCommandParam::Ignore,
4897 InsertParagraphCommand::GetInstance,
4898 CommandOnTextEditor::Enabled));
4899 sInternalCommandDataHashtable->InsertOrUpdate(
4900 u"insertlinebreak"_ns,
4901 InternalCommandData(
4902 "cmd_insertLineBreak",
4903 Command::InsertLineBreak,
4904 ExecCommandParam::Ignore,
4905 InsertLineBreakCommand::GetInstance,
4906 CommandOnTextEditor::Enabled));
4907 sInternalCommandDataHashtable->InsertOrUpdate(
4908 u"formatblock"_ns,
4909 InternalCommandData(
4910 "cmd_paragraphState",
4911 Command::FormatBlock,
4912 ExecCommandParam::String,
4913 ParagraphStateCommand::GetInstance,
4914 CommandOnTextEditor::Disabled));
4915 sInternalCommandDataHashtable->InsertOrUpdate(
4916 u"heading"_ns,
4917 InternalCommandData(
4918 "cmd_paragraphState",
4919 Command::FormatBlock,
4920 ExecCommandParam::String,
4921 ParagraphStateCommand::GetInstance,
4922 CommandOnTextEditor::Disabled));
4923 sInternalCommandDataHashtable->InsertOrUpdate(
4924 u"styleWithCSS"_ns,
4925 InternalCommandData(
4926 "cmd_setDocumentUseCSS",
4927 Command::SetDocumentUseCSS,
4928 ExecCommandParam::Boolean,
4929 SetDocumentStateCommand::GetInstance,
4930 CommandOnTextEditor::FallThrough));
4931 sInternalCommandDataHashtable->InsertOrUpdate(
4932 u"usecss"_ns, // Legacy command
4933 InternalCommandData(
4934 "cmd_setDocumentUseCSS",
4935 Command::SetDocumentUseCSS,
4936 ExecCommandParam::InvertedBoolean,
4937 SetDocumentStateCommand::GetInstance,
4938 CommandOnTextEditor::FallThrough));
4939 sInternalCommandDataHashtable->InsertOrUpdate(
4940 u"contentReadOnly"_ns,
4941 InternalCommandData(
4942 "cmd_setDocumentReadOnly",
4943 Command::SetDocumentReadOnly,
4944 ExecCommandParam::Boolean,
4945 SetDocumentStateCommand::GetInstance,
4946 CommandOnTextEditor::Enabled));
4947 sInternalCommandDataHashtable->InsertOrUpdate(
4948 u"readonly"_ns, // Legacy command
4949 InternalCommandData(
4950 "cmd_setDocumentReadOnly",
4951 Command::SetDocumentReadOnly,
4952 ExecCommandParam::InvertedBoolean,
4953 SetDocumentStateCommand::GetInstance,
4954 CommandOnTextEditor::Enabled));
4955 sInternalCommandDataHashtable->InsertOrUpdate(
4956 u"insertBrOnReturn"_ns,
4957 InternalCommandData(
4958 "cmd_insertBrOnReturn",
4959 Command::SetDocumentInsertBROnEnterKeyPress,
4960 ExecCommandParam::Boolean,
4961 SetDocumentStateCommand::GetInstance,
4962 CommandOnTextEditor::FallThrough));
4963 sInternalCommandDataHashtable->InsertOrUpdate(
4964 u"defaultParagraphSeparator"_ns,
4965 InternalCommandData(
4966 "cmd_defaultParagraphSeparator",
4967 Command::SetDocumentDefaultParagraphSeparator,
4968 ExecCommandParam::String,
4969 SetDocumentStateCommand::GetInstance,
4970 CommandOnTextEditor::FallThrough));
4971 sInternalCommandDataHashtable->InsertOrUpdate(
4972 u"enableObjectResizing"_ns,
4973 InternalCommandData(
4974 "cmd_enableObjectResizing",
4975 Command::ToggleObjectResizers,
4976 ExecCommandParam::Boolean,
4977 SetDocumentStateCommand::GetInstance,
4978 CommandOnTextEditor::FallThrough));
4979 sInternalCommandDataHashtable->InsertOrUpdate(
4980 u"enableInlineTableEditing"_ns,
4981 InternalCommandData(
4982 "cmd_enableInlineTableEditing",
4983 Command::ToggleInlineTableEditor,
4984 ExecCommandParam::Boolean,
4985 SetDocumentStateCommand::GetInstance,
4986 CommandOnTextEditor::FallThrough));
4987 sInternalCommandDataHashtable->InsertOrUpdate(
4988 u"enableAbsolutePositionEditing"_ns,
4989 InternalCommandData(
4990 "cmd_enableAbsolutePositionEditing",
4991 Command::ToggleAbsolutePositionEditor,
4992 ExecCommandParam::Boolean,
4993 SetDocumentStateCommand::GetInstance,
4994 CommandOnTextEditor::FallThrough));
4995 #if 0
4996 // with empty string
4997 sInternalCommandDataHashtable->InsertOrUpdate(
4998 u"justifynone"_ns,
4999 InternalCommandData(
5000 "cmd_align",
5001 Command::Undefined,
5002 ExecCommandParam::Ignore,
5003 nullptr,
5004 CommandOnTextEditor::Disabled)); // Not implemented yet.
5005 // REQUIRED SPECIAL REVIEW special review
5006 sInternalCommandDataHashtable->InsertOrUpdate(
5007 u"saveas"_ns,
5008 InternalCommandData(
5009 "cmd_saveAs",
5010 Command::Undefined,
5011 ExecCommandParam::Boolean,
5012 nullptr,
5013 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5014 // REQUIRED SPECIAL REVIEW special review
5015 sInternalCommandDataHashtable->InsertOrUpdate(
5016 u"print"_ns,
5017 InternalCommandData(
5018 "cmd_print",
5019 Command::Undefined,
5020 ExecCommandParam::Boolean,
5021 nullptr,
5022 CommandOnTextEditor::FallThrough)); // Not implemented yet.
5023 #endif // #if 0
5024 // clang-format on
5027 Document::InternalCommandData Document::ConvertToInternalCommand(
5028 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
5029 nsAString* aAdjustedValue /* = nullptr */) {
5030 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
5031 EnsureInitializeInternalCommandDataHashtable();
5032 InternalCommandData commandData;
5033 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
5034 return InternalCommandData();
5036 // Ignore if the command is disabled by a corresponding pref due to Gecko
5037 // specific.
5038 switch (commandData.mCommand) {
5039 case Command::FormatIncreaseFontSize:
5040 MOZ_DIAGNOSTIC_ASSERT(
5041 aHTMLCommandName.LowerCaseEqualsLiteral("increasefontsize"));
5042 if (!StaticPrefs::dom_document_edit_command_increasefontsize_enabled()) {
5043 return InternalCommandData();
5045 break;
5046 case Command::FormatDecreaseFontSize:
5047 MOZ_DIAGNOSTIC_ASSERT(
5048 aHTMLCommandName.LowerCaseEqualsLiteral("decreasefontsize"));
5049 if (!StaticPrefs::dom_document_edit_command_decreasefontsize_enabled()) {
5050 return InternalCommandData();
5052 break;
5053 case Command::GetHTML:
5054 MOZ_DIAGNOSTIC_ASSERT(aHTMLCommandName.LowerCaseEqualsLiteral("gethtml"));
5055 if (!StaticPrefs::dom_document_edit_command_gethtml_enabled()) {
5056 return InternalCommandData();
5058 break;
5059 case Command::FormatBlock:
5060 if (!StaticPrefs::dom_document_edit_command_heading_enabled() &&
5061 aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5062 return InternalCommandData();
5064 break;
5065 case Command::SetDocumentReadOnly:
5066 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
5067 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
5068 return InternalCommandData();
5070 if (!StaticPrefs::dom_document_edit_command_readonly_enabled() &&
5071 aHTMLCommandName.LowerCaseEqualsLiteral("readonly")) {
5072 return InternalCommandData();
5074 break;
5075 case Command::SetDocumentInsertBROnEnterKeyPress:
5076 MOZ_DIAGNOSTIC_ASSERT(
5077 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
5078 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
5079 return InternalCommandData();
5081 break;
5082 default:
5083 break;
5085 if (!aAdjustedValue) {
5086 // No further work to do
5087 return commandData;
5089 switch (commandData.mExecCommandParam) {
5090 case ExecCommandParam::Ignore:
5091 // Just have to copy it, no checking
5092 switch (commandData.mCommand) {
5093 case Command::FormatJustifyLeft:
5094 aAdjustedValue->AssignLiteral("left");
5095 break;
5096 case Command::FormatJustifyRight:
5097 aAdjustedValue->AssignLiteral("right");
5098 break;
5099 case Command::FormatJustifyCenter:
5100 aAdjustedValue->AssignLiteral("center");
5101 break;
5102 case Command::FormatJustifyFull:
5103 aAdjustedValue->AssignLiteral("justify");
5104 break;
5105 default:
5106 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5107 EditorCommandParamType::None);
5108 break;
5110 return commandData;
5112 case ExecCommandParam::Boolean:
5113 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5114 EditorCommandParamType::Bool));
5115 // If this is a boolean value and it's not explicitly false (e.g. no
5116 // value). We default to "true" (see bug 301490).
5117 if (!aValue.LowerCaseEqualsLiteral("false")) {
5118 aAdjustedValue->AssignLiteral("true");
5119 } else {
5120 aAdjustedValue->AssignLiteral("false");
5122 return commandData;
5124 case ExecCommandParam::InvertedBoolean:
5125 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5126 EditorCommandParamType::Bool));
5127 // For old backwards commands we invert the check.
5128 if (aValue.LowerCaseEqualsLiteral("false")) {
5129 aAdjustedValue->AssignLiteral("true");
5130 } else {
5131 aAdjustedValue->AssignLiteral("false");
5133 return commandData;
5135 case ExecCommandParam::String:
5136 MOZ_ASSERT(!!(
5137 EditorCommand::GetParamType(commandData.mCommand) &
5138 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5139 switch (commandData.mCommand) {
5140 case Command::FormatBlock: {
5141 const char16_t* start = aValue.BeginReading();
5142 const char16_t* end = aValue.EndReading();
5143 if (start != end && *start == '<' && *(end - 1) == '>') {
5144 ++start;
5145 --end;
5147 // XXX Should we reorder this array with actual usage?
5148 static const nsStaticAtom* kFormattableBlockTags[] = {
5149 // clang-format off
5150 nsGkAtoms::address,
5151 nsGkAtoms::blockquote,
5152 nsGkAtoms::dd,
5153 nsGkAtoms::div,
5154 nsGkAtoms::dl,
5155 nsGkAtoms::dt,
5156 nsGkAtoms::h1,
5157 nsGkAtoms::h2,
5158 nsGkAtoms::h3,
5159 nsGkAtoms::h4,
5160 nsGkAtoms::h5,
5161 nsGkAtoms::h6,
5162 nsGkAtoms::p,
5163 nsGkAtoms::pre,
5164 // clang-format on
5166 nsAutoString value(nsDependentSubstring(start, end));
5167 ToLowerCase(value);
5168 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5169 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5170 if (valueAtom == kTag) {
5171 kTag->ToString(*aAdjustedValue);
5172 return commandData;
5175 return InternalCommandData();
5177 case Command::FormatFontSize: {
5178 // Per editing spec as of April 23, 2012, we need to reject the value
5179 // if it's not a valid floating-point number surrounded by optional
5180 // whitespace. Otherwise, we parse it as a legacy font size. For
5181 // now, we just parse as a legacy font size regardless (matching
5182 // WebKit) -- bug 747879.
5183 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
5184 if (!size) {
5185 return InternalCommandData();
5187 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5188 aAdjustedValue->AppendInt(size);
5189 return commandData;
5191 case Command::InsertImage:
5192 case Command::InsertLink:
5193 if (aValue.IsEmpty()) {
5194 // Invalid value, return false
5195 return InternalCommandData();
5197 aAdjustedValue->Assign(aValue);
5198 return commandData;
5199 case Command::SetDocumentDefaultParagraphSeparator:
5200 if (!aValue.LowerCaseEqualsLiteral("div") &&
5201 !aValue.LowerCaseEqualsLiteral("p") &&
5202 !aValue.LowerCaseEqualsLiteral("br")) {
5203 // Invalid value
5204 return InternalCommandData();
5206 aAdjustedValue->Assign(aValue);
5207 return commandData;
5208 default:
5209 aAdjustedValue->Assign(aValue);
5210 return commandData;
5213 default:
5214 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5215 return InternalCommandData();
5219 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5220 Document& aDocument, const InternalCommandData& aCommandData)
5221 : mCommandData(aCommandData) {
5222 // We'll retrieve an editor with current DOM tree and layout information.
5223 // However, JS may have already hidden or remove exposed root content of
5224 // the editor. Therefore, we need the latest layout information here.
5225 aDocument.FlushPendingNotifications(FlushType::Layout);
5226 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5227 mDoNothing = true;
5228 return;
5231 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5232 // Consider context of command handling which is automatically resolved
5233 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5234 // The order is:
5235 // 1. TextEditor if there is an active element and it has TextEditor like
5236 // <input type="text"> or <textarea>.
5237 // 2. HTMLEditor for the document, if there is.
5238 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5239 if (aCommandData.IsCutOrCopyCommand()) {
5240 // Note that we used to use DocShell to handle `cut` and `copy` command
5241 // for dispatching corresponding events for making possible web apps to
5242 // implement their own editor without editable elements but supports
5243 // standard shortcut keys, etc. In this case, we prefer to use active
5244 // element's editor to keep same behavior.
5245 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5246 } else {
5247 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5248 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5249 if (!mActiveEditor) {
5250 mActiveEditor = mHTMLEditor;
5255 // Then, retrieve editor command class instance which should handle it
5256 // and can handle it now.
5257 if (!mActiveEditor) {
5258 // If the command is available without editor, we should redirect the
5259 // command to focused descendant with DocShell.
5260 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5261 mDoNothing = true;
5262 return;
5264 return;
5267 // Otherwise, we should use EditorCommand instance (which is singleton
5268 // instance) when it's enabled.
5269 mEditorCommand = aCommandData.mGetEditorCommandFunc
5270 ? aCommandData.mGetEditorCommandFunc()
5271 : nullptr;
5272 if (!mEditorCommand) {
5273 mDoNothing = true;
5274 mActiveEditor = nullptr;
5275 mHTMLEditor = nullptr;
5276 return;
5279 if (IsCommandEnabled()) {
5280 return;
5283 // If the EditorCommand instance is disabled, we should do nothing if
5284 // the command requires an editor.
5285 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5286 // Do nothing if editor specific commands is disabled (bug 760052).
5287 mDoNothing = true;
5288 return;
5291 // Otherwise, we should redirect it to focused descendant with DocShell.
5292 mEditorCommand = nullptr;
5293 mActiveEditor = nullptr;
5294 mHTMLEditor = nullptr;
5297 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5298 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5299 switch (mCommandData.mCommandOnTextEditor) {
5300 case CommandOnTextEditor::Enabled:
5301 return mActiveEditor;
5302 case CommandOnTextEditor::Disabled:
5303 return mActiveEditor && mActiveEditor->IsTextEditor()
5304 ? nullptr
5305 : mActiveEditor.get();
5306 case CommandOnTextEditor::FallThrough:
5307 return mHTMLEditor;
5309 return nullptr;
5312 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5313 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5314 // Make sure frames are up to date, since that can affect whether
5315 // we're editable.
5316 doc->FlushPendingNotifications(FlushType::Frames);
5318 EditorBase* targetEditor = GetTargetEditor();
5319 if (targetEditor && targetEditor->IsTextEditor()) {
5320 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5321 // "readonly" too.
5322 return !targetEditor->IsReadonly();
5324 return aDocument->IsEditingOn();
5327 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5328 EditorBase* targetEditor = GetTargetEditor();
5329 if (!targetEditor) {
5330 return false;
5332 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5333 return MOZ_KnownLive(mEditorCommand)
5334 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5337 nsresult Document::AutoEditorCommandTarget::DoCommand(
5338 nsIPrincipal* aPrincipal) const {
5339 MOZ_ASSERT(!DoNothing());
5340 MOZ_ASSERT(mEditorCommand);
5341 EditorBase* targetEditor = GetTargetEditor();
5342 if (!targetEditor) {
5343 return NS_SUCCESS_DOM_NO_OPERATION;
5345 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5346 return MOZ_KnownLive(mEditorCommand)
5347 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5348 aPrincipal);
5351 template <typename ParamType>
5352 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5353 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5354 MOZ_ASSERT(!DoNothing());
5355 MOZ_ASSERT(mEditorCommand);
5356 EditorBase* targetEditor = GetTargetEditor();
5357 if (!targetEditor) {
5358 return NS_SUCCESS_DOM_NO_OPERATION;
5360 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5361 return MOZ_KnownLive(mEditorCommand)
5362 ->DoCommandParam(mCommandData.mCommand, aParam,
5363 MOZ_KnownLive(*targetEditor), aPrincipal);
5366 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5367 nsCommandParams& aParams) const {
5368 MOZ_ASSERT(mEditorCommand);
5369 EditorBase* targetEditor = GetTargetEditor();
5370 if (!targetEditor) {
5371 return NS_OK;
5373 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5374 return MOZ_KnownLive(mEditorCommand)
5375 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5376 MOZ_KnownLive(targetEditor), nullptr);
5379 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5380 const nsAString& aValue,
5381 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5382 // Only allow on HTML documents.
5383 if (!IsHTMLOrXHTML()) {
5384 aRv.ThrowInvalidStateError(
5385 "execCommand is only supported on HTML documents");
5386 return false;
5388 // Otherwise, don't throw exception for compatibility with Chrome.
5390 // if they are requesting UI from us, let's fail since we have no UI
5391 if (aShowUI) {
5392 return false;
5395 // If we're running an execCommand, we should just return false.
5396 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5397 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5398 mIsRunningExecCommand) {
5399 return false;
5402 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5403 // this might add some ugly JS dependencies?
5405 nsAutoString adjustedValue;
5406 InternalCommandData commandData =
5407 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5408 switch (commandData.mCommand) {
5409 case Command::DoNothing:
5410 // "gethtml" command is a command to retrieve a string value, not executing
5411 // anything and not enough the `bool` value of `execCommand`. So, at here,
5412 // we do nothing for "gethtml" command.
5413 case Command::GetHTML:
5414 return false;
5415 case Command::FormatIncreaseFontSize:
5416 SetUseCounter(eUseCounter_custom_DocumentExecCommandIncreaseFontSize);
5417 break;
5418 case Command::FormatDecreaseFontSize:
5419 SetUseCounter(eUseCounter_custom_DocumentExecCommandDecreaseFontSize);
5420 break;
5421 case Command::FormatBlock:
5422 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5423 SetUseCounter(eUseCounter_custom_DocumentExecCommandHeading);
5425 break;
5426 case Command::SetDocumentReadOnly:
5427 SetUseCounter(aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5428 ? eUseCounter_custom_DocumentExecCommandContentReadOnly
5429 : eUseCounter_custom_DocumentExecCommandReadOnly);
5430 break;
5431 default:
5432 break;
5435 // Do security check first.
5436 if (commandData.IsCutOrCopyCommand()) {
5437 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5438 // We have rejected the event due to it not being performed in an
5439 // input-driven context therefore, we report the error to the console.
5440 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5441 this, nsContentUtils::eDOM_PROPERTIES,
5442 "ExecCommandCutCopyDeniedNotInputDriven");
5443 return false;
5445 } else if (commandData.IsPasteCommand()) {
5446 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5447 nsGkAtoms::clipboardRead)) {
5448 return false;
5452 AutoRunningExecCommandMarker markRunningExecCommand(*this);
5454 // Next, consider context of command handling which is automatically resolved
5455 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5456 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5457 if (commandData.IsAvailableOnlyWhenEditable() &&
5458 !editCommandTarget.IsEditable(this)) {
5459 return false;
5462 if (editCommandTarget.DoNothing()) {
5463 return false;
5466 // If we cannot use EditorCommand instance directly, we need to handle the
5467 // command with traditional path (i.e., with DocShell or nsCommandManager).
5468 if (!editCommandTarget.IsEditor()) {
5469 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5471 // Special case clipboard write commands like Command::Cut and
5472 // Command::Copy. For such commands, we need the behaviour from
5473 // nsWindowRoot::GetControllers() which is to look at the focused element,
5474 // and defer to a focused textbox's controller. The code past taken by
5475 // other commands in ExecCommand() always uses the window directly, rather
5476 // than deferring to the textbox, which is desireable for most editor
5477 // commands, but not these commands (as those should allow copying out of
5478 // embedded editors). This behaviour is invoked if we call DoCommand()
5479 // directly on the docShell.
5480 // XXX This means that we allow web app to pick up selected content in
5481 // descendant document and write it into the clipboard when a
5482 // descendant document has focus. However, Chromium does not allow
5483 // this and this seems that it's not good behavior from point of view
5484 // of security. We should treat this issue in another bug.
5485 if (commandData.IsCutOrCopyCommand()) {
5486 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5487 if (!docShell) {
5488 return false;
5490 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5491 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5492 return false;
5494 return NS_SUCCEEDED(rv);
5497 // Otherwise (currently, only clipboard read commands like Command::Paste),
5498 // we don't need to redirect the command to focused subdocument.
5499 // Therefore, we should handle it with nsCommandManager as used to be.
5500 // It may dispatch only preceding event of editing on non-editable element
5501 // to make web apps possible to handle standard shortcut key, etc in
5502 // their own editor.
5503 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5504 if (!commandManager) {
5505 return false;
5508 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5509 if (!window) {
5510 return false;
5513 // Return false for disabled commands (bug 760052)
5514 if (!commandManager->IsCommandEnabled(
5515 nsDependentCString(commandData.mXULCommandName), window)) {
5516 return false;
5519 MOZ_ASSERT(commandData.IsPasteCommand() ||
5520 commandData.mCommand == Command::SelectAll);
5521 nsresult rv =
5522 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5523 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5526 // Now, our target is fixed to the editor. So, we can use EditorCommand
5527 // in EditorCommandTarget directly.
5529 EditorCommandParamType paramType =
5530 EditorCommand::GetParamType(commandData.mCommand);
5532 // If we don't have meaningful parameter or the EditorCommand does not
5533 // require additional parameter, we can use `DoCommand()`.
5534 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5535 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5536 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5537 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5540 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5541 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5542 // a `bool` value.
5543 if (!!(paramType & EditorCommandParamType::Bool)) {
5544 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5545 adjustedValue.EqualsLiteral("false"));
5546 nsresult rv = editCommandTarget.DoCommandParam(
5547 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5548 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5551 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5552 // in this case. However, `paramType` may contain both `String` and
5553 // `CString` but in such case, we should use `DoCommandParam()` which
5554 // takes `nsAString`. So, we should check whether `paramType` contains
5555 // `String` or not first.
5556 if (!!(paramType & EditorCommandParamType::String)) {
5557 MOZ_ASSERT(!adjustedValue.IsVoid());
5558 nsresult rv =
5559 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5560 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5563 // Finally, `paramType` should have `CString`. We should use
5564 // `DoCommandParam()` which takes `nsACString`.
5565 if (!!(paramType & EditorCommandParamType::CString)) {
5566 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5567 MOZ_ASSERT(!utf8Value.IsVoid());
5568 nsresult rv =
5569 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5570 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5573 MOZ_ASSERT_UNREACHABLE(
5574 "Not yet implemented to handle new EditorCommandParamType");
5575 return false;
5578 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5579 nsIPrincipal& aSubjectPrincipal,
5580 ErrorResult& aRv) {
5581 // Only allow on HTML documents.
5582 if (!IsHTMLOrXHTML()) {
5583 aRv.ThrowInvalidStateError(
5584 "queryCommandEnabled is only supported on HTML documents");
5585 return false;
5587 // Otherwise, don't throw exception for compatibility with Chrome.
5589 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5590 switch (commandData.mCommand) {
5591 case Command::DoNothing:
5592 return false;
5593 case Command::FormatIncreaseFontSize:
5594 SetUseCounter(
5595 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledIncreaseFontSize);
5596 break;
5597 case Command::FormatDecreaseFontSize:
5598 SetUseCounter(
5599 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledDecreaseFontSize);
5600 break;
5601 case Command::GetHTML:
5602 SetUseCounter(
5603 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledGetHTML);
5604 break;
5605 case Command::FormatBlock:
5606 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5607 SetUseCounter(
5608 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledHeading);
5610 break;
5611 case Command::SetDocumentReadOnly:
5612 SetUseCounter(
5613 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5614 ? eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly
5615 : eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledReadOnly);
5616 break;
5617 case Command::SetDocumentInsertBROnEnterKeyPress:
5618 SetUseCounter(
5619 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5620 break;
5621 default:
5622 break;
5625 // cut & copy are always allowed
5626 if (commandData.IsCutOrCopyCommand()) {
5627 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5630 // Report false for restricted commands
5631 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5632 return false;
5635 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5636 if (commandData.IsAvailableOnlyWhenEditable() &&
5637 !editCommandTarget.IsEditable(this)) {
5638 return false;
5641 if (editCommandTarget.IsEditor()) {
5642 return editCommandTarget.IsCommandEnabled();
5645 // get command manager and dispatch command to our window if it's acceptable
5646 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5647 if (!commandManager) {
5648 return false;
5651 nsPIDOMWindowOuter* window = GetWindow();
5652 if (!window) {
5653 return false;
5656 return commandManager->IsCommandEnabled(
5657 nsDependentCString(commandData.mXULCommandName), window);
5660 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5661 ErrorResult& aRv) {
5662 // Only allow on HTML documents.
5663 if (!IsHTMLOrXHTML()) {
5664 aRv.ThrowInvalidStateError(
5665 "queryCommandIndeterm is only supported on HTML documents");
5666 return false;
5668 // Otherwise, don't throw exception for compatibility with Chrome.
5670 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5671 if (commandData.mCommand == Command::DoNothing) {
5672 return false;
5675 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5676 if (commandData.IsAvailableOnlyWhenEditable() &&
5677 !editCommandTarget.IsEditable(this)) {
5678 return false;
5680 RefPtr<nsCommandParams> params = new nsCommandParams();
5681 if (editCommandTarget.IsEditor()) {
5682 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5683 return false;
5685 } else {
5686 // get command manager and dispatch command to our window if it's acceptable
5687 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5688 if (!commandManager) {
5689 return false;
5692 nsPIDOMWindowOuter* window = GetWindow();
5693 if (!window) {
5694 return false;
5697 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5698 window, params))) {
5699 return false;
5703 // If command does not have a state_mixed value, this call fails and sets
5704 // retval to false. This is fine -- we want to return false in that case
5705 // anyway (bug 738385), so we just don't throw regardless.
5706 return params->GetBool("state_mixed");
5709 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5710 ErrorResult& aRv) {
5711 // Only allow on HTML documents.
5712 if (!IsHTMLOrXHTML()) {
5713 aRv.ThrowInvalidStateError(
5714 "queryCommandState is only supported on HTML documents");
5715 return false;
5717 // Otherwise, don't throw exception for compatibility with Chrome.
5719 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5720 switch (commandData.mCommand) {
5721 case Command::DoNothing:
5722 return false;
5723 case Command::GetHTML:
5724 SetUseCounter(eUseCounter_custom_DocumentQueryCommandStateOrValueGetHTML);
5725 break;
5726 case Command::FormatBlock:
5727 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5728 SetUseCounter(
5729 eUseCounter_custom_DocumentQueryCommandStateOrValueHeading);
5731 break;
5732 case Command::SetDocumentReadOnly:
5733 SetUseCounter(
5734 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5735 ? eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly
5736 : eUseCounter_custom_DocumentQueryCommandStateOrValueReadOnly);
5737 break;
5738 case Command::SetDocumentInsertBROnEnterKeyPress:
5739 SetUseCounter(
5740 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5741 break;
5742 default:
5743 break;
5746 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5747 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5748 // return false always.
5749 return false;
5752 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5753 if (commandData.IsAvailableOnlyWhenEditable() &&
5754 !editCommandTarget.IsEditable(this)) {
5755 return false;
5757 RefPtr<nsCommandParams> params = new nsCommandParams();
5758 if (editCommandTarget.IsEditor()) {
5759 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5760 return false;
5762 } else {
5763 // get command manager and dispatch command to our window if it's acceptable
5764 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5765 if (!commandManager) {
5766 return false;
5769 nsPIDOMWindowOuter* window = GetWindow();
5770 if (!window) {
5771 return false;
5774 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5775 window, params))) {
5776 return false;
5780 // handle alignment as a special case (possibly other commands too?)
5781 // Alignment is special because the external api is individual
5782 // commands but internally we use cmd_align with different
5783 // parameters. When getting the state of this command, we need to
5784 // return the boolean for this particular alignment rather than the
5785 // string of 'which alignment is this?'
5786 switch (commandData.mCommand) {
5787 case Command::FormatJustifyLeft: {
5788 nsAutoCString currentValue;
5789 nsresult rv = params->GetCString("state_attribute", currentValue);
5790 if (NS_FAILED(rv)) {
5791 return false;
5793 return currentValue.EqualsLiteral("left");
5795 case Command::FormatJustifyRight: {
5796 nsAutoCString currentValue;
5797 nsresult rv = params->GetCString("state_attribute", currentValue);
5798 if (NS_FAILED(rv)) {
5799 return false;
5801 return currentValue.EqualsLiteral("right");
5803 case Command::FormatJustifyCenter: {
5804 nsAutoCString currentValue;
5805 nsresult rv = params->GetCString("state_attribute", currentValue);
5806 if (NS_FAILED(rv)) {
5807 return false;
5809 return currentValue.EqualsLiteral("center");
5811 case Command::FormatJustifyFull: {
5812 nsAutoCString currentValue;
5813 nsresult rv = params->GetCString("state_attribute", currentValue);
5814 if (NS_FAILED(rv)) {
5815 return false;
5817 return currentValue.EqualsLiteral("justify");
5819 default:
5820 break;
5823 // If command does not have a state_all value, this call fails and sets
5824 // retval to false. This is fine -- we want to return false in that case
5825 // anyway (bug 738385), so we just succeed and return false regardless.
5826 return params->GetBool("state_all");
5829 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5830 CallerType aCallerType, ErrorResult& aRv) {
5831 // Only allow on HTML documents.
5832 if (!IsHTMLOrXHTML()) {
5833 aRv.ThrowInvalidStateError(
5834 "queryCommandSupported is only supported on HTML documents");
5835 return false;
5837 // Otherwise, don't throw exception for compatibility with Chrome.
5839 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5840 switch (commandData.mCommand) {
5841 case Command::DoNothing:
5842 return false;
5843 case Command::FormatIncreaseFontSize:
5844 SetUseCounter(
5845 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledIncreaseFontSize);
5846 break;
5847 case Command::FormatDecreaseFontSize:
5848 SetUseCounter(
5849 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledDecreaseFontSize);
5850 break;
5851 case Command::GetHTML:
5852 SetUseCounter(
5853 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledGetHTML);
5854 break;
5855 case Command::FormatBlock:
5856 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5857 SetUseCounter(
5858 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledHeading);
5860 break;
5861 case Command::SetDocumentReadOnly:
5862 SetUseCounter(
5863 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5864 ? eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly
5865 : eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledReadOnly);
5866 break;
5867 case Command::SetDocumentInsertBROnEnterKeyPress:
5868 SetUseCounter(
5869 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5870 break;
5871 default:
5872 break;
5875 // Gecko technically supports all the clipboard commands including
5876 // cut/copy/paste, but non-privileged content will be unable to call
5877 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5878 // may also be disallowed to be called from non-privileged content.
5879 // For that reason, we report the support status of corresponding
5880 // command accordingly.
5881 if (aCallerType != CallerType::System) {
5882 if (commandData.IsPasteCommand()) {
5883 return false;
5885 if (commandData.IsCutOrCopyCommand() &&
5886 !StaticPrefs::dom_allow_cut_copy()) {
5887 // XXXbz should we worry about correctly reporting "true" in the
5888 // "restricted, but we're an addon with clipboardWrite permissions" case?
5889 // See also nsContentUtils::IsCutCopyAllowed.
5890 return false;
5894 // aHTMLCommandName is supported if it can be converted to a Midas command
5895 return true;
5898 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5899 nsAString& aValue, ErrorResult& aRv) {
5900 aValue.Truncate();
5902 // Only allow on HTML documents.
5903 if (!IsHTMLOrXHTML()) {
5904 aRv.ThrowInvalidStateError(
5905 "queryCommandValue is only supported on HTML documents");
5906 return;
5908 // Otherwise, don't throw exception for compatibility with Chrome.
5910 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5911 switch (commandData.mCommand) {
5912 case Command::DoNothing:
5913 // Return empty string
5914 return;
5915 case Command::GetHTML:
5916 SetUseCounter(eUseCounter_custom_DocumentQueryCommandStateOrValueGetHTML);
5917 break;
5918 case Command::FormatBlock:
5919 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5920 SetUseCounter(
5921 eUseCounter_custom_DocumentQueryCommandStateOrValueHeading);
5923 break;
5924 case Command::SetDocumentReadOnly:
5925 SetUseCounter(
5926 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5927 ? eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly
5928 : eUseCounter_custom_DocumentQueryCommandStateOrValueReadOnly);
5929 break;
5930 case Command::SetDocumentInsertBROnEnterKeyPress:
5931 SetUseCounter(
5932 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5933 break;
5934 default:
5935 break;
5938 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5939 if (commandData.IsAvailableOnlyWhenEditable() &&
5940 !editCommandTarget.IsEditable(this)) {
5941 return;
5943 RefPtr<nsCommandParams> params = new nsCommandParams();
5944 // FYI: Only GetHTML command is not implemented by editor. Use window's
5945 // command table instead.
5946 if (editCommandTarget.IsEditor()) {
5947 MOZ_ASSERT(commandData.mCommand != Command::GetHTML);
5948 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5949 return;
5952 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5953 return;
5955 } else {
5956 // get command manager and dispatch command to our window if it's acceptable
5957 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5958 if (!commandManager) {
5959 return;
5962 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5963 if (!window) {
5964 return;
5967 // this is a special command since we are calling DoCommand rather than
5968 // GetCommandState like the other commands
5969 if (commandData.mCommand == Command::GetHTML) {
5970 if (NS_FAILED(params->SetBool("selection_only", true))) {
5971 return;
5973 if (NS_FAILED(params->SetCString("format", "text/html"_ns))) {
5974 return;
5976 if (NS_FAILED(commandManager->DoCommand(commandData.mXULCommandName,
5977 params, window))) {
5978 return;
5980 params->GetString("result", aValue);
5981 return;
5984 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5985 return;
5988 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5989 window, params))) {
5990 return;
5994 // If command does not have a state_attribute value, this call fails, and
5995 // aValue will wind up being the empty string. This is fine -- we want to
5996 // return "" in that case anyway (bug 738385), so we just return NS_OK
5997 // regardless.
5998 nsAutoCString result;
5999 params->GetCString("state_attribute", result);
6000 CopyUTF8toUTF16(result, aValue);
6003 void Document::MaybeEditingStateChanged() {
6004 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
6005 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
6006 if (nsContentUtils::IsSafeToRunScript()) {
6007 EditingStateChanged();
6008 } else if (!mInDestructor) {
6009 nsContentUtils::AddScriptRunner(
6010 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
6011 &Document::MaybeEditingStateChanged));
6016 void Document::NotifyFetchOrXHRSuccess() {
6017 if (mShouldNotifyFetchSuccess) {
6018 nsContentUtils::DispatchEventOnlyToChrome(
6019 this, ToSupports(this), u"DOMDocFetchSuccess"_ns, CanBubble::eNo,
6020 Cancelable::eNo, /* DefaultAction */ nullptr);
6024 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
6025 mShouldNotifyFetchSuccess = aShouldNotify;
6028 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
6029 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
6032 void Document::TearingDownEditor() {
6033 if (IsEditingOn()) {
6034 mEditingState = EditingState::eTearingDown;
6035 if (IsHTMLOrXHTML()) {
6036 RemoveContentEditableStyleSheets();
6041 nsresult Document::TurnEditingOff() {
6042 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
6044 nsPIDOMWindowOuter* window = GetWindow();
6045 if (!window) {
6046 return NS_ERROR_FAILURE;
6049 nsIDocShell* docshell = window->GetDocShell();
6050 if (!docshell) {
6051 return NS_ERROR_FAILURE;
6054 bool isBeingDestroyed = false;
6055 docshell->IsBeingDestroyed(&isBeingDestroyed);
6056 if (isBeingDestroyed) {
6057 return NS_ERROR_FAILURE;
6060 nsCOMPtr<nsIEditingSession> editSession;
6061 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6062 NS_ENSURE_SUCCESS(rv, rv);
6064 // turn editing off
6065 rv = editSession->TearDownEditorOnWindow(window);
6066 NS_ENSURE_SUCCESS(rv, rv);
6068 mEditingState = EditingState::eOff;
6070 // Editor resets selection since it is being destroyed. But if focus is
6071 // still into editable control, we have to initialize selection again.
6072 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
6073 if (RefPtr<TextControlElement> textControlElement =
6074 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
6075 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
6076 textEditor->ReinitializeSelection(*textControlElement);
6081 return NS_OK;
6084 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
6085 nsIDocShell* docShell = aWindow->GetDocShell();
6086 if (!docShell) {
6087 return false;
6089 return docShell->GetPresShell() != nullptr;
6092 nsresult Document::EditingStateChanged() {
6093 if (mRemovedFromDocShell) {
6094 return NS_OK;
6097 if (mEditingState == EditingState::eSettingUp ||
6098 mEditingState == EditingState::eTearingDown) {
6099 // XXX We shouldn't recurse
6100 return NS_OK;
6103 const bool designMode = IsInDesignMode();
6104 EditingState newState =
6105 designMode ? EditingState::eDesignMode
6106 : (mContentEditableCount > 0 ? EditingState::eContentEditable
6107 : EditingState::eOff);
6108 if (mEditingState == newState) {
6109 // No changes in editing mode.
6110 return NS_OK;
6113 if (newState == EditingState::eOff) {
6114 // Editing is being turned off.
6115 nsAutoScriptBlocker scriptBlocker;
6116 NotifyEditableStateChange(*this);
6117 return TurnEditingOff();
6120 // Flush out style changes on our _parent_ document, if any, so that
6121 // our check for a presshell won't get stale information.
6122 if (mParentDocument) {
6123 mParentDocument->FlushPendingNotifications(FlushType::Style);
6126 // get editing session, make sure this is a strong reference so the
6127 // window can't get deleted during the rest of this call.
6128 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6129 if (!window) {
6130 return NS_ERROR_FAILURE;
6133 nsIDocShell* docshell = window->GetDocShell();
6134 if (!docshell) {
6135 return NS_ERROR_FAILURE;
6138 // FlushPendingNotifications might destroy our docshell.
6139 bool isBeingDestroyed = false;
6140 docshell->IsBeingDestroyed(&isBeingDestroyed);
6141 if (isBeingDestroyed) {
6142 return NS_ERROR_FAILURE;
6145 nsCOMPtr<nsIEditingSession> editSession;
6146 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6147 NS_ENSURE_SUCCESS(rv, rv);
6149 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6150 if (htmlEditor) {
6151 // We might already have an editor if it was set up for mail, let's see
6152 // if this is actually the case.
6153 uint32_t flags = 0;
6154 htmlEditor->GetFlags(&flags);
6155 if (flags & nsIEditor::eEditorMailMask) {
6156 // We already have a mail editor, then we should not attempt to create
6157 // another one.
6158 return NS_OK;
6162 if (!HasPresShell(window)) {
6163 // We should not make the window editable or setup its editor.
6164 // It's probably style=display:none.
6165 return NS_OK;
6168 bool makeWindowEditable = mEditingState == EditingState::eOff;
6169 bool spellRecheckAll = false;
6170 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6171 htmlEditor = nullptr;
6174 EditingState oldState = mEditingState;
6175 nsAutoEditingState push(this, EditingState::eSettingUp);
6177 RefPtr<PresShell> presShell = GetPresShell();
6178 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6180 // If we're entering the design mode from non-editable state, put the
6181 // selection at the beginning of the document for compatibility reasons.
6182 bool collapseSelectionAtBeginningOfDocument =
6183 designMode && oldState == EditingState::eOff;
6184 // However, mEditingState may be eOff even if there is some
6185 // `contenteditable` area and selection has been initialized for it because
6186 // mEditingState for `contenteditable` may have been scheduled to modify
6187 // when safe. In such case, we should not reinitialize selection.
6188 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6189 Selection* selection =
6190 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6191 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6192 if (selection && selection->RangeCount()) {
6193 // Perhaps, we don't need to check whether the selection is in
6194 // an editing host or not because all contents will be editable
6195 // in designMode. (And we don't want to make this code so complicated
6196 // because of legacy API.)
6197 collapseSelectionAtBeginningOfDocument = false;
6201 MOZ_ASSERT(mStyleSetFilled);
6203 // Before making this window editable, we need to modify UA style sheet
6204 // because new style may change whether focused element will be focusable
6205 // or not.
6206 if (IsHTMLOrXHTML()) {
6207 AddContentEditableStyleSheetsToStyleSet(designMode);
6210 if (designMode) {
6211 // designMode is being turned on (overrides contentEditable).
6212 spellRecheckAll = oldState == EditingState::eContentEditable;
6215 // Adjust focused element with new style but blur event shouldn't be fired
6216 // until mEditingState is modified with newState.
6217 nsAutoScriptBlocker scriptBlocker;
6218 if (designMode) {
6219 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6220 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6221 window, nsFocusManager::eOnlyCurrentWindow,
6222 getter_AddRefs(focusedWindow));
6223 if (focusedContent) {
6224 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6225 bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
6226 : !focusedContent->IsFocusable();
6227 if (clearFocus) {
6228 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
6229 fm->ClearFocus(window);
6230 // If we need to dispatch blur event, we should put off after
6231 // modifying mEditingState since blur event handler may change
6232 // designMode state again.
6233 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6239 if (makeWindowEditable) {
6240 // Editing is being turned on (through designMode or contentEditable)
6241 // Turn on editor.
6242 // XXX This can cause flushing which can change the editing state, so make
6243 // sure to avoid recursing.
6244 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6245 NS_ENSURE_SUCCESS(rv, rv);
6248 // XXX Need to call TearDownEditorOnWindow for all failures.
6249 htmlEditor = docshell->GetHTMLEditor();
6250 if (!htmlEditor) {
6251 // Return NS_OK even though we've failed to create an editor here. This
6252 // is so that the setter of designMode on non-HTML documents does not
6253 // fail.
6254 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6255 // would detect that we can't support the mimetype if appropriate and
6256 // would fall onto the eEditorErrorCantEditMimeType path.
6257 return NS_OK;
6260 if (collapseSelectionAtBeginningOfDocument) {
6261 htmlEditor->BeginningOfDocument();
6264 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6265 nsContentUtils::AddScriptBlocker();
6269 mEditingState = newState;
6270 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6271 nsContentUtils::RemoveScriptBlocker();
6272 // If mEditingState is overwritten by another call and already disabled
6273 // the editing, we shouldn't keep making window editable.
6274 if (mEditingState == EditingState::eOff) {
6275 return NS_OK;
6279 if (makeWindowEditable) {
6280 // TODO: We should do this earlier in this method.
6281 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6282 // whose argument is false here. Then, if it returns error, we
6283 // stopped making it editable. However, after bug 1697078 fixed,
6284 // `ExecCommand` returns error only when the document is not XHTML's
6285 // nor HTML's. Therefore, we use same error handling for now.
6286 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6287 // Editor setup failed. Editing is not on after all.
6288 // XXX Should we reset the editable flag on nodes?
6289 editSession->TearDownEditorOnWindow(window);
6290 mEditingState = EditingState::eOff;
6291 return NS_ERROR_DOM_INVALID_STATE_ERR;
6293 // Set the editor to not insert <br> elements on return when in <p> elements
6294 // by default.
6295 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6298 // Resync the editor's spellcheck state.
6299 if (spellRecheckAll) {
6300 nsCOMPtr<nsISelectionController> selectionController =
6301 htmlEditor->GetSelectionController();
6302 if (NS_WARN_IF(!selectionController)) {
6303 return NS_ERROR_FAILURE;
6306 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6307 nsISelectionController::SELECTION_SPELLCHECK);
6308 if (spellCheckSelection) {
6309 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6312 htmlEditor->SyncRealTimeSpell();
6314 MaybeDispatchCheckKeyPressEventModelEvent();
6316 return NS_OK;
6319 // Helper class, used below in ChangeContentEditableCount().
6320 class DeferredContentEditableCountChangeEvent : public Runnable {
6321 public:
6322 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6323 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6324 mDoc(aDoc),
6325 mElement(aElement) {}
6327 NS_IMETHOD Run() override {
6328 if (mElement && mElement->OwnerDoc() == mDoc) {
6329 mDoc->DeferredContentEditableCountChange(mElement);
6331 return NS_OK;
6334 private:
6335 RefPtr<Document> mDoc;
6336 RefPtr<Element> mElement;
6339 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6340 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6341 "Trying to decrement too much.");
6343 mContentEditableCount += aChange;
6345 nsContentUtils::AddScriptRunner(
6346 new DeferredContentEditableCountChangeEvent(this, aElement));
6349 void Document::DeferredContentEditableCountChange(Element* aElement) {
6350 if (mParser ||
6351 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6352 return;
6355 EditingState oldState = mEditingState;
6357 nsresult rv = EditingStateChanged();
6358 NS_ENSURE_SUCCESS_VOID(rv);
6360 if (oldState == mEditingState &&
6361 mEditingState == EditingState::eContentEditable) {
6362 // We just changed the contentEditable state of a node, we need to reset
6363 // the spellchecking state of that node.
6364 if (aElement) {
6365 nsPIDOMWindowOuter* window = GetWindow();
6366 if (!window) {
6367 return;
6370 nsIDocShell* docshell = window->GetDocShell();
6371 if (!docshell) {
6372 return;
6375 RefPtr<HTMLEditor> htmlEditor = docshell->GetHTMLEditor();
6376 if (htmlEditor) {
6377 RefPtr<nsRange> range = nsRange::Create(aElement);
6378 IgnoredErrorResult res;
6379 range->SelectNode(*aElement, res);
6380 if (res.Failed()) {
6381 // The node might be detached from the document at this point,
6382 // which would cause this call to fail. In this case, we can
6383 // safely ignore the contenteditable count change.
6384 return;
6387 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6388 rv = htmlEditor->GetInlineSpellChecker(false,
6389 getter_AddRefs(spellChecker));
6390 NS_ENSURE_SUCCESS_VOID(rv);
6392 if (spellChecker) {
6393 rv = spellChecker->SpellCheckRange(range);
6394 NS_ENSURE_SUCCESS_VOID(rv);
6401 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6402 // Currently, we need to check only when we're becoming editable for
6403 // contenteditable.
6404 if (mEditingState != EditingState::eContentEditable) {
6405 return;
6408 if (mHasBeenEditable) {
6409 return;
6411 mHasBeenEditable = true;
6413 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6414 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6415 // with proper keypress event for the active web app.
6416 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6417 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6418 checkEvent.mFlags.mCancelable = false;
6419 checkEvent.mFlags.mBubbles = false;
6420 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6421 // Post the event rather than dispatching it synchronously because we need
6422 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6423 // can avoid paying unnecessary runtime cost for most web apps.
6424 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6427 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6428 PresShell* presShell = GetPresShell();
6429 if (!presShell) {
6430 return;
6432 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6435 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6437 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6438 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6439 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6440 aFocusTime >= mLastFocusTime);
6441 mLastFocusTime = aFocusTime;
6444 void Document::GetReferrer(nsAString& aReferrer) const {
6445 aReferrer.Truncate();
6446 if (!mReferrerInfo) {
6447 return;
6450 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6451 if (!referrer) {
6452 return;
6455 nsAutoCString uri;
6456 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
6457 if (NS_WARN_IF(NS_FAILED(rv))) {
6458 return;
6461 CopyUTF8toUTF16(uri, aReferrer);
6464 void Document::GetCookie(nsAString& aCookie, ErrorResult& rv) {
6465 aCookie.Truncate(); // clear current cookie in case service fails;
6466 // no cookie isn't an error condition.
6468 if (mDisableCookieAccess) {
6469 return;
6472 // If the document's sandboxed origin flag is set, access to read cookies
6473 // is prohibited.
6474 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6475 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
6476 return;
6479 StorageAccess storageAccess = StorageAllowedForDocument(this);
6480 if (storageAccess == StorageAccess::eDeny) {
6481 return;
6484 if (ShouldPartitionStorage(storageAccess) &&
6485 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6486 return;
6489 // If the document is a cookie-averse Document... return the empty string.
6490 if (IsCookieAverse()) {
6491 return;
6494 // not having a cookie service isn't an error
6495 nsCOMPtr<nsICookieService> service =
6496 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6497 if (service) {
6498 nsAutoCString cookie;
6499 service->GetCookieStringFromDocument(this, cookie);
6500 // CopyUTF8toUTF16 doesn't handle error
6501 // because it assumes that the input is valid.
6502 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6506 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6507 if (mDisableCookieAccess) {
6508 return;
6511 // If the document's sandboxed origin flag is set, access to write cookies
6512 // is prohibited.
6513 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6514 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
6515 return;
6518 StorageAccess storageAccess = StorageAllowedForDocument(this);
6519 if (storageAccess == StorageAccess::eDeny) {
6520 return;
6523 if (ShouldPartitionStorage(storageAccess) &&
6524 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6525 return;
6528 // If the document is a cookie-averse Document... do nothing.
6529 if (IsCookieAverse()) {
6530 return;
6533 if (!mDocumentURI) {
6534 return;
6537 // not having a cookie service isn't an error
6538 nsCOMPtr<nsICookieService> service =
6539 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6540 if (!service) {
6541 return;
6544 NS_ConvertUTF16toUTF8 cookie(aCookie);
6545 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6547 // No warning messages here.
6548 if (NS_FAILED(rv)) {
6549 return;
6552 nsCOMPtr<nsIObserverService> observerService =
6553 mozilla::services::GetObserverService();
6554 if (observerService) {
6555 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6556 nsString(aCookie).get());
6560 ReferrerPolicy Document::GetReferrerPolicy() const {
6561 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6562 : ReferrerPolicy::_empty;
6565 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6566 aAlinkColor.Truncate();
6568 HTMLBodyElement* body = GetBodyElement();
6569 if (body) {
6570 body->GetALink(aAlinkColor);
6574 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6575 HTMLBodyElement* body = GetBodyElement();
6576 if (body) {
6577 body->SetALink(aAlinkColor);
6581 void Document::GetLinkColor(nsAString& aLinkColor) {
6582 aLinkColor.Truncate();
6584 HTMLBodyElement* body = GetBodyElement();
6585 if (body) {
6586 body->GetLink(aLinkColor);
6590 void Document::SetLinkColor(const nsAString& aLinkColor) {
6591 HTMLBodyElement* body = GetBodyElement();
6592 if (body) {
6593 body->SetLink(aLinkColor);
6597 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6598 aVlinkColor.Truncate();
6600 HTMLBodyElement* body = GetBodyElement();
6601 if (body) {
6602 body->GetVLink(aVlinkColor);
6606 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6607 HTMLBodyElement* body = GetBodyElement();
6608 if (body) {
6609 body->SetVLink(aVlinkColor);
6613 void Document::GetBgColor(nsAString& aBgColor) {
6614 aBgColor.Truncate();
6616 HTMLBodyElement* body = GetBodyElement();
6617 if (body) {
6618 body->GetBgColor(aBgColor);
6622 void Document::SetBgColor(const nsAString& aBgColor) {
6623 HTMLBodyElement* body = GetBodyElement();
6624 if (body) {
6625 body->SetBgColor(aBgColor);
6629 void Document::GetFgColor(nsAString& aFgColor) {
6630 aFgColor.Truncate();
6632 HTMLBodyElement* body = GetBodyElement();
6633 if (body) {
6634 body->GetText(aFgColor);
6638 void Document::SetFgColor(const nsAString& aFgColor) {
6639 HTMLBodyElement* body = GetBodyElement();
6640 if (body) {
6641 body->SetText(aFgColor);
6645 void Document::CaptureEvents() {
6646 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6649 void Document::ReleaseEvents() {
6650 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6653 HTMLAllCollection* Document::All() {
6654 if (!mAll) {
6655 mAll = new HTMLAllCollection(this);
6657 return mAll;
6660 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6661 if (mIsSrcdocDocument) {
6662 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6663 if (inStrmChan) {
6664 return inStrmChan->GetSrcdocData(aSrcdocData);
6667 aSrcdocData = VoidString();
6668 return NS_OK;
6671 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6672 nsPIDOMWindowOuter* win = GetWindow();
6673 if (!win) {
6674 return nullptr;
6676 return WindowProxyHolder(win->GetBrowsingContext());
6679 nsIContent* Document::GetUnretargetedFocusedContent() const {
6680 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6681 if (!window) {
6682 return nullptr;
6684 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6685 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6686 window, nsFocusManager::eOnlyCurrentWindow,
6687 getter_AddRefs(focusedWindow));
6688 if (!focusedContent) {
6689 return nullptr;
6691 // be safe and make sure the element is from this document
6692 if (focusedContent->OwnerDoc() != this) {
6693 return nullptr;
6696 if (focusedContent->ChromeOnlyAccess()) {
6697 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6699 return focusedContent;
6702 Element* Document::GetActiveElement() {
6703 // Get the focused element.
6704 Element* focusedElement = GetRetargetedFocusedElement();
6705 if (focusedElement) {
6706 return focusedElement;
6709 // No focused element anywhere in this document. Try to get the BODY.
6710 if (IsHTMLOrXHTML()) {
6711 Element* bodyElement = AsHTMLDocument()->GetBody();
6712 if (bodyElement) {
6713 return bodyElement;
6715 // Special case to handle the transition to XHTML from XUL documents
6716 // where there currently isn't a body element, but we need to match the
6717 // XUL behavior. This should be removed when bug 1540278 is resolved.
6718 if (nsContentUtils::IsChromeDoc(this)) {
6719 Element* docElement = GetDocumentElement();
6720 if (docElement && docElement->IsXULElement()) {
6721 return docElement;
6724 // Because of IE compatibility, return null when html document doesn't have
6725 // a body.
6726 return nullptr;
6729 // If we couldn't get a BODY, return the root element.
6730 return GetDocumentElement();
6733 Element* Document::GetCurrentScript() {
6734 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6735 return el;
6738 void Document::ReleaseCapture() const {
6739 // only release the capture if the caller can access it. This prevents a
6740 // page from stopping a scrollbar grab for example.
6741 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6742 if (node && nsContentUtils::CanCallerAccess(node)) {
6743 PresShell::ReleaseCapturingContent();
6747 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6748 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6749 return mChromeXHRDocBaseURI;
6752 return GetDocBaseURI();
6755 void Document::SetBaseURI(nsIURI* aURI) {
6756 if (!aURI && !mDocumentBaseURI) {
6757 return;
6760 // Don't do anything if the URI wasn't actually changed.
6761 if (aURI && mDocumentBaseURI) {
6762 bool equalBases = false;
6763 mDocumentBaseURI->Equals(aURI, &equalBases);
6764 if (equalBases) {
6765 return;
6769 mDocumentBaseURI = aURI;
6770 RefreshLinkHrefs();
6773 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6774 const nsAString& aURI) {
6775 RefPtr<nsIURI> resolvedURI;
6776 MOZ_TRY(
6777 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6778 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6781 URLExtraData* Document::DefaultStyleAttrURLData() {
6782 MOZ_ASSERT(NS_IsMainThread());
6783 nsIURI* baseURI = GetDocBaseURI();
6784 nsIPrincipal* principal = NodePrincipal();
6785 bool equals;
6786 if (!mCachedURLData || mCachedURLData->BaseURI() != baseURI ||
6787 mCachedURLData->Principal() != principal || !mCachedReferrerInfo ||
6788 NS_FAILED(mCachedURLData->ReferrerInfo()->Equals(mCachedReferrerInfo,
6789 &equals)) ||
6790 !equals) {
6791 mCachedReferrerInfo = ReferrerInfo::CreateForInternalCSSResources(this);
6792 mCachedURLData = new URLExtraData(baseURI, mCachedReferrerInfo, principal);
6794 return mCachedURLData;
6797 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6798 if (mCharacterSet != aEncoding) {
6799 mCharacterSet = aEncoding;
6800 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6801 RecomputeLanguageFromCharset();
6803 if (nsPresContext* context = GetPresContext()) {
6804 context->DocumentCharSetChanged(aEncoding);
6809 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6810 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6813 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6814 aData.Truncate();
6815 const HeaderData* data = mHeaderData.get();
6816 while (data) {
6817 if (data->mField == aHeaderField) {
6818 aData = data->mData;
6819 break;
6821 data = data->mNext.get();
6825 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6826 if (!aHeaderField) {
6827 NS_ERROR("null headerField");
6828 return;
6831 if (!mHeaderData) {
6832 if (!aData.IsEmpty()) { // don't bother storing empty string
6833 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
6835 } else {
6836 HeaderData* data = mHeaderData.get();
6837 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
6838 bool found = false;
6839 do { // look for existing and replace
6840 if (data->mField == aHeaderField) {
6841 if (!aData.IsEmpty()) {
6842 data->mData.Assign(aData);
6843 } else { // don't store empty string
6844 // Note that data->mNext is moved to a temporary before the old value
6845 // of *lastPtr is deleted.
6846 *lastPtr = std::move(data->mNext);
6848 found = true;
6850 break;
6852 lastPtr = &data->mNext;
6853 data = lastPtr->get();
6854 } while (data);
6856 if (!aData.IsEmpty() && !found) {
6857 // didn't find, append
6858 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
6862 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6863 CopyUTF16toUTF8(aData, mContentLanguage);
6864 mMayNeedFontPrefsUpdate = true;
6865 if (auto* presContext = GetPresContext()) {
6866 presContext->ContentLanguageChanged();
6870 if (aHeaderField == nsGkAtoms::origin_trial) {
6871 mTrials.UpdateFromToken(aData, NodePrincipal());
6874 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6875 SetPreferredStyleSheetSet(aData);
6878 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6879 // We get into this code before we have a script global yet, so get to our
6880 // container via mDocumentContainer.
6881 if (mDocumentContainer) {
6882 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6883 // (used to just use the current URI of our webnavigation, but that
6884 // should really be the same thing). Note that this code can run
6885 // before the current URI of the webnavigation has been updated, so we
6886 // can't assert equality here.
6887 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
6891 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6892 mAllowDNSPrefetch) {
6893 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6894 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6897 if (aHeaderField == nsGkAtoms::handheldFriendly) {
6898 mViewportType = Unknown;
6902 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6903 NotNull<const Encoding*>& aEncoding,
6904 nsHtml5TreeOpExecutor* aExecutor) {
6905 if (aChannel) {
6906 nsAutoCString charsetVal;
6907 nsresult rv = aChannel->GetContentCharset(charsetVal);
6908 if (NS_SUCCEEDED(rv)) {
6909 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6910 if (preferred) {
6911 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
6912 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
6914 aEncoding = WrapNotNull(preferred);
6915 aCharsetSource = kCharsetFromChannel;
6916 return;
6917 } else if (aExecutor && !charsetVal.IsEmpty()) {
6918 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
6924 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6925 #ifdef DEBUG
6926 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6927 const Element* element = Element::FromNode(node);
6928 if (!element) {
6929 continue;
6931 MOZ_ASSERT(!element->HasServoData());
6933 #endif
6936 already_AddRefed<PresShell> Document::CreatePresShell(
6937 nsPresContext* aContext, nsViewManager* aViewManager) {
6938 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6940 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6942 AssertNoStaleServoDataIn(*this);
6944 RefPtr<PresShell> presShell = new PresShell(this);
6945 // Note: we don't hold a ref to the shell (it holds a ref to us)
6946 mPresShell = presShell;
6948 if (!mStyleSetFilled) {
6949 FillStyleSet();
6952 presShell->Init(aContext, aViewManager);
6954 // Gaining a shell causes changes in how media queries are evaluated, so
6955 // invalidate that.
6956 aContext->MediaFeatureValuesChanged(
6957 {MediaFeatureChange::kAllChanges},
6958 MediaFeatureChangePropagation::JustThisDocument);
6960 // Make sure to never paint if we belong to an invisible DocShell.
6961 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6962 if (docShell && docShell->IsInvisible()) {
6963 presShell->SetNeverPainting(true);
6966 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6967 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6968 presShell.get(), docShell.get()));
6970 mExternalResourceMap.ShowViewers();
6972 UpdateFrameRequestCallbackSchedulingState();
6974 if (mDocumentL10n) {
6975 // In case we already accumulated mutations,
6976 // we'll trigger the refresh driver now.
6977 mDocumentL10n->OnCreatePresShell();
6980 // Now that we have a shell, we might have @font-face rules (the presence of a
6981 // shell may change which rules apply to us). We don't need to do anything
6982 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
6983 // is ready to update we'll flush the font set.
6984 MarkUserFontSetDirty();
6986 // Take the author style disabled state from the top browsing cvontext.
6987 // (PageStyleChild.jsm ensures this is up to date.)
6988 if (BrowsingContext* bc = GetBrowsingContext()) {
6989 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
6992 return presShell.forget();
6995 void Document::UpdateFrameRequestCallbackSchedulingState(
6996 PresShell* aOldPresShell) {
6997 // If this condition changes to depend on some other variable, make sure to
6998 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
6999 // that variable can change. Also consider if you should change
7000 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
7001 // condition.
7002 bool shouldBeScheduled =
7003 WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
7004 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
7005 // nothing to do
7006 return;
7009 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
7010 MOZ_RELEASE_ASSERT(presShell);
7012 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
7013 if (shouldBeScheduled) {
7014 rd->ScheduleFrameRequestCallbacks(this);
7015 } else {
7016 rd->RevokeFrameRequestCallbacks(this);
7019 mFrameRequestCallbacksScheduled = shouldBeScheduled;
7022 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
7023 MOZ_ASSERT(aCallbacks.IsEmpty());
7024 mFrameRequestManager.Take(aCallbacks);
7025 // No need to manually remove ourselves from the refresh driver; it will
7026 // handle that part. But we do have to update our state.
7027 mFrameRequestCallbacksScheduled = false;
7030 bool Document::ShouldThrottleFrameRequests() const {
7031 if (mStaticCloneCount > 0) {
7032 // Even if we're not visible, a static clone may be, so run at full speed.
7033 return false;
7036 if (Hidden()) {
7037 // We're not visible (probably in a background tab or the bf cache).
7038 return true;
7041 if (!mPresShell) {
7042 // Can't do anything smarter. We don't run frame requests in documents
7043 // without a pres shell anyways.
7044 return false;
7047 if (!mPresShell->IsActive()) {
7048 // The pres shell is not active (we're an invisible OOP iframe or such), so
7049 // throttle.
7050 return true;
7053 if (mPresShell->IsPaintingSuppressed()) {
7054 // Historically we have throttled frame requests until we've painted at
7055 // least once, so keep doing that.
7056 return true;
7059 Element* el = GetEmbedderElement();
7060 if (!el) {
7061 // If we're not in-process, our refresh driver is throttled separately (via
7062 // PresShell::SetIsActive, so not much more we can do here.
7063 return false;
7066 if (!StaticPrefs::layout_throttle_in_process_iframes()) {
7067 return false;
7070 // Note that because we have to scroll this document into view at least once
7071 // to unthrottle it, we will drop one requestAnimationFrame frame when a
7072 // document that previously wasn't visible scrolls into view. This is
7073 // acceptable / unlikely to be human-perceivable, though we could improve on
7074 // it if needed by adding an intersection margin or something of that sort.
7075 const IntersectionInput input = DOMIntersectionObserver::ComputeInput(
7076 *el->OwnerDoc(), /* aRoot = */ nullptr, /* aMargin = */ nullptr);
7077 const IntersectionOutput output =
7078 DOMIntersectionObserver::Intersect(input, *el);
7079 return !output.Intersects();
7082 void Document::DeletePresShell() {
7083 mExternalResourceMap.HideViewers();
7084 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
7085 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
7088 // When our shell goes away, request that all our images be immediately
7089 // discarded, so we don't carry around decoded image data for a document we
7090 // no longer intend to paint.
7091 ImageTracker()->RequestDiscardAll();
7093 // Now that we no longer have a shell, we need to forget about any FontFace
7094 // objects for @font-face rules that came from the style set. There's no need
7095 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
7096 // no point on it.
7097 MarkUserFontSetDirty();
7099 if (mResizeObserverController) {
7100 mResizeObserverController->ShellDetachedFromDocument();
7103 if (IsEditingOn()) {
7104 TurnEditingOff();
7107 PresShell* oldPresShell = mPresShell;
7108 mPresShell = nullptr;
7109 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
7111 ClearStaleServoData();
7112 AssertNoStaleServoDataIn(*this);
7114 mStyleSet->ShellDetachedFromDocument();
7115 mStyleSetFilled = false;
7116 mQuirkSheetAdded = false;
7117 mContentEditableSheetAdded = false;
7118 mDesignModeSheetAdded = false;
7121 void Document::DisallowBFCaching(uint32_t aStatus) {
7122 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7123 if (!mBFCacheDisallowed) {
7124 WindowGlobalChild* wgc = GetWindowGlobalChild();
7125 if (wgc) {
7126 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7129 mBFCacheDisallowed = true;
7132 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7133 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7135 if (mPresShell) {
7136 if (aEntry) {
7137 mPresShell->StopObservingRefreshDriver();
7138 } else if (mBFCacheEntry) {
7139 mPresShell->StartObservingRefreshDriver();
7142 mBFCacheEntry = aEntry;
7145 bool Document::RemoveFromBFCacheSync() {
7146 bool removed = false;
7147 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7148 entry->RemoveFromBFCacheSync();
7149 removed = true;
7150 } else if (!IsCurrentActiveDocument()) {
7151 // In the old bfcache implementation while the new page is loading, but
7152 // before nsIContentViewer.show() has been called, the previous page doesn't
7153 // yet have nsIBFCacheEntry. However, the previous page isn't the current
7154 // active document anymore.
7155 DisallowBFCaching();
7156 removed = true;
7159 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7160 if (BrowsingContext* bc = GetBrowsingContext()) {
7161 if (bc->IsInBFCache()) {
7162 ContentChild* cc = ContentChild::GetSingleton();
7163 // IPC is asynchronous but the caller is supposed to check the return
7164 // value. The reason for 'Sync' in the method name is that the old
7165 // implementation may run scripts. There is Async variant in
7166 // the old session history implementation for the cases where
7167 // synchronous operation isn't safe.
7168 cc->SendRemoveFromBFCache(bc->Top());
7169 removed = true;
7173 return removed;
7176 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7177 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7179 NS_RELEASE(e->mKey);
7180 if (e->mSubDocument) {
7181 e->mSubDocument->SetParentDocument(nullptr);
7182 NS_RELEASE(e->mSubDocument);
7186 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7187 SubDocMapEntry* e =
7188 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7190 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7191 NS_ADDREF(e->mKey);
7193 e->mSubDocument = nullptr;
7196 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7197 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7199 if (!aSubDoc) {
7200 // aSubDoc is nullptr, remove the mapping
7202 if (mSubDocuments) {
7203 mSubDocuments->Remove(aElement);
7205 } else {
7206 if (!mSubDocuments) {
7207 // Create a new hashtable
7209 static const PLDHashTableOps hash_table_ops = {
7210 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7211 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7213 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
7216 // Add a mapping to the hash table
7217 auto entry =
7218 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7220 if (!entry) {
7221 return NS_ERROR_OUT_OF_MEMORY;
7224 if (entry->mSubDocument) {
7225 entry->mSubDocument->SetParentDocument(nullptr);
7227 // Release the old sub document
7228 NS_RELEASE(entry->mSubDocument);
7231 entry->mSubDocument = aSubDoc;
7232 NS_ADDREF(entry->mSubDocument);
7234 aSubDoc->SetParentDocument(this);
7237 return NS_OK;
7240 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7241 if (mSubDocuments && aContent->IsElement()) {
7242 auto entry = static_cast<SubDocMapEntry*>(
7243 mSubDocuments->Search(aContent->AsElement()));
7245 if (entry) {
7246 return entry->mSubDocument;
7250 return nullptr;
7253 Element* Document::GetEmbedderElement() const {
7254 // We check if we're the active document in our BrowsingContext
7255 // by comparing against its document, rather than checking if the
7256 // WindowContext is cached, since mWindow may be null when we're
7257 // called (such as in nsPresContext::Init).
7258 if (BrowsingContext* bc = GetBrowsingContext()) {
7259 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7262 return nullptr;
7265 bool Document::IsNodeOfType(uint32_t aFlags) const { return false; }
7267 Element* Document::GetRootElement() const {
7268 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7269 ? mCachedRootElement
7270 : GetRootElementInternal();
7273 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7275 Element* Document::GetRootElementInternal() const {
7276 // We invoke GetRootElement() immediately before the servo traversal, so we
7277 // should always have a cache hit from Servo.
7278 MOZ_ASSERT(NS_IsMainThread());
7280 // Loop backwards because any non-elements, such as doctypes and PIs
7281 // are likely to appear before the root element.
7282 for (nsIContent* child = GetLastChild(); child;
7283 child = child->GetPreviousSibling()) {
7284 if (Element* element = Element::FromNode(child)) {
7285 const_cast<Document*>(this)->mCachedRootElement = element;
7286 return element;
7290 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7291 return nullptr;
7294 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7295 bool aNotify, ErrorResult& aRv) {
7296 if (aKid->IsElement() && GetRootElement()) {
7297 NS_WARNING("Inserting root element when we already have one");
7298 aRv.ThrowHierarchyRequestError("There is already a root element.");
7299 return;
7302 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7305 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7306 Maybe<mozAutoDocUpdate> updateBatch;
7307 if (aKid->IsElement()) {
7308 updateBatch.emplace(this, aNotify);
7309 // Destroy the link map up front before we mess with the child list.
7310 DestroyElementMaps();
7313 // Preemptively clear mCachedRootElement, since we may be about to remove it
7314 // from our child list, and we don't want to return this maybe-obsolete value
7315 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7316 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7317 // GetRootElement() calls until after it's removed the child from mChildren.
7318 // Any call before that point would restore this soon-to-be-obsolete cached
7319 // answer, and our clearing here would be fruitless.)
7320 mCachedRootElement = nullptr;
7321 nsINode::RemoveChildNode(aKid, aNotify);
7322 MOZ_ASSERT(mCachedRootElement != aKid,
7323 "Stale pointer in mCachedRootElement, after we tried to clear it "
7324 "(maybe somebody called GetRootElement() too early?)");
7327 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7328 if (mStyleSetFilled) {
7329 mStyleSet->AddDocStyleSheet(aSheet);
7330 ApplicableStylesChanged();
7334 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7335 mStyleSet->RecordShadowStyleChange(aShadowRoot);
7336 ApplicableStylesChanged();
7339 void Document::ApplicableStylesChanged() {
7340 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7341 // we need to always track style changes and remove the mStyleSetFilled.
7342 if (!mStyleSetFilled) {
7343 return;
7346 MarkUserFontSetDirty();
7347 PresShell* ps = GetPresShell();
7348 if (!ps) {
7349 return;
7352 ps->EnsureStyleFlush();
7353 nsPresContext* pc = ps->GetPresContext();
7354 if (!pc) {
7355 return;
7358 pc->MarkCounterStylesDirty();
7359 pc->MarkFontFeatureValuesDirty();
7360 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7363 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7364 if (mStyleSetFilled) {
7365 mStyleSet->RemoveStyleSheet(aSheet);
7366 ApplicableStylesChanged();
7370 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7371 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7373 if (aSheet.IsApplicable()) {
7374 AddStyleSheetToStyleSets(aSheet);
7378 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7379 const bool applicable = aSheet.IsApplicable();
7380 // If we're actually in the document style sheet list
7381 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7382 if (applicable) {
7383 AddStyleSheetToStyleSets(aSheet);
7384 } else {
7385 RemoveStyleSheetFromStyleSets(aSheet);
7389 PostStyleSheetApplicableStateChangeEvent(aSheet);
7391 if (!mSSApplicableStateNotificationPending) {
7392 MOZ_RELEASE_ASSERT(NS_IsMainThread());
7393 nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(
7394 "Document::NotifyStyleSheetApplicableStateChanged", this,
7395 &Document::NotifyStyleSheetApplicableStateChanged);
7396 mSSApplicableStateNotificationPending =
7397 NS_SUCCEEDED(Dispatch(TaskCategory::Other, notification.forget()));
7401 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7402 if (!StyleSheetChangeEventsEnabled()) {
7403 return;
7406 StyleSheetApplicableStateChangeEventInit init;
7407 init.mBubbles = true;
7408 init.mCancelable = true;
7409 init.mStylesheet = &aSheet;
7410 init.mApplicable = aSheet.IsApplicable();
7412 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7413 StyleSheetApplicableStateChangeEvent::Constructor(
7414 this, u"StyleSheetApplicableStateChanged"_ns, init);
7415 event->SetTrusted(true);
7416 event->SetTarget(this);
7417 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7418 new AsyncEventDispatcher(this, event);
7419 asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
7420 asyncDispatcher->PostDOMEvent();
7423 void Document::NotifyStyleSheetApplicableStateChanged() {
7424 mSSApplicableStateNotificationPending = false;
7425 nsCOMPtr<nsIObserverService> observerService =
7426 mozilla::services::GetObserverService();
7427 if (observerService) {
7428 observerService->NotifyObservers(
7429 ToSupports(this), "style-sheet-applicable-state-changed", nullptr);
7433 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7434 nsIURI* aSheetURI) {
7435 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7436 bool bEqual;
7437 nsIURI* uri = aSheets[i]->GetSheetURI();
7439 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7440 return i;
7443 return -1;
7446 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7447 nsIURI* aSheetURI) {
7448 MOZ_ASSERT(aSheetURI, "null arg");
7450 // Checking if we have loaded this one already.
7451 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7452 return NS_ERROR_INVALID_ARG;
7454 // Loading the sheet sync.
7455 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7457 css::SheetParsingMode parsingMode;
7458 switch (aType) {
7459 case Document::eAgentSheet:
7460 parsingMode = css::eAgentSheetFeatures;
7461 break;
7463 case Document::eUserSheet:
7464 parsingMode = css::eUserSheetFeatures;
7465 break;
7467 case Document::eAuthorSheet:
7468 parsingMode = css::eAuthorSheetFeatures;
7469 break;
7471 default:
7472 MOZ_CRASH("impossible value for aType");
7475 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7476 css::Loader::UseSystemPrincipal::Yes);
7477 if (result.isErr()) {
7478 return result.unwrapErr();
7481 RefPtr<StyleSheet> sheet = result.unwrap();
7483 sheet->SetAssociatedDocumentOrShadowRoot(this);
7484 MOZ_ASSERT(sheet->IsApplicable());
7486 return AddAdditionalStyleSheet(aType, sheet);
7489 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7490 StyleSheet* aSheet) {
7491 if (mAdditionalSheets[aType].Contains(aSheet)) {
7492 return NS_ERROR_INVALID_ARG;
7495 if (!aSheet->IsApplicable()) {
7496 return NS_ERROR_INVALID_ARG;
7499 mAdditionalSheets[aType].AppendElement(aSheet);
7501 if (mStyleSetFilled) {
7502 mStyleSet->AppendStyleSheet(*aSheet);
7503 ApplicableStylesChanged();
7505 return NS_OK;
7508 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7509 nsIURI* aSheetURI) {
7510 MOZ_ASSERT(aSheetURI);
7512 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7514 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7515 if (i >= 0) {
7516 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7517 sheets.RemoveElementAt(i);
7519 if (!mIsGoingAway) {
7520 MOZ_ASSERT(sheetRef->IsApplicable());
7521 if (mStyleSetFilled) {
7522 mStyleSet->RemoveStyleSheet(*sheetRef);
7523 ApplicableStylesChanged();
7526 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7530 nsIGlobalObject* Document::GetScopeObject() const {
7531 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7532 return scope;
7535 bool Document::CrossOriginIsolated() const {
7536 if (auto* bc = GetBrowsingContext()) {
7537 return bc->CrossOriginIsolated();
7540 // For a data document without a browsing context we check the
7541 // cross-origin-isolated state from its creator's inner window.
7542 if (mLoadedAsData) {
7543 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
7544 return window && window->GetBrowsingContext() &&
7545 window->GetBrowsingContext()->CrossOriginIsolated();
7548 return false;
7551 DocGroup* Document::GetDocGroupOrCreate() {
7552 if (!mDocGroup) {
7553 nsAutoCString docGroupKey;
7554 nsresult rv = mozilla::dom::DocGroup::GetKey(
7555 NodePrincipal(), CrossOriginIsolated(), docGroupKey);
7556 if (NS_SUCCEEDED(rv) && mDocumentContainer) {
7557 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7558 if (group) {
7559 mDocGroup = group->AddDocument(docGroupKey, this);
7563 return mDocGroup;
7566 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7567 mScopeObject = do_GetWeakReference(aGlobal);
7568 if (aGlobal) {
7569 mHasHadScriptHandlingObject = true;
7571 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
7572 if (!window) {
7573 return;
7575 BrowsingContextGroup* browsingContextGroup =
7576 window->GetBrowsingContextGroup();
7578 // We should already have the principal, and now that we have been added
7579 // to a window, we should be able to join a DocGroup!
7580 nsAutoCString docGroupKey;
7581 nsresult rv = mozilla::dom::DocGroup::GetKey(
7582 NodePrincipal(), CrossOriginIsolated(), docGroupKey);
7583 if (mDocGroup) {
7584 if (NS_SUCCEEDED(rv)) {
7585 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7587 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7588 browsingContextGroup);
7589 } else {
7590 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7592 MOZ_ASSERT(mDocGroup);
7595 MOZ_ASSERT_IF(
7596 mNodeInfoManager->GetArenaAllocator(),
7597 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7601 bool Document::ContainsEMEContent() {
7602 nsPIDOMWindowInner* win = GetInnerWindow();
7603 // Note this case is different from checking just media elements in that
7604 // it covers when we've created MediaKeys but not associated them with a
7605 // media element.
7606 return win && win->HasActiveMediaKeysInstance();
7609 bool Document::ContainsMSEContent() {
7610 bool containsMSE = false;
7612 auto check = [&containsMSE](nsISupports* aSupports) {
7613 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7614 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7615 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7616 if (ms) {
7617 containsMSE = true;
7622 EnumerateActivityObservers(check);
7623 return containsMSE;
7626 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
7627 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7628 if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7629 mediaElem->NotifyOwnerDocumentActivityChanged();
7631 nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
7632 do_QueryInterface(aSupports));
7633 if (objectLoadingContent) {
7634 nsObjectLoadingContent* olc =
7635 static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
7636 olc->NotifyOwnerDocumentActivityChanged();
7638 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7639 do_QueryInterface(aSupports));
7640 if (objectDocumentActivity) {
7641 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7642 } else {
7643 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7644 do_QueryInterface(aSupports));
7645 if (imageLoadingContent) {
7646 auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7647 ilc->NotifyOwnerDocumentActivityChanged();
7652 void Document::NotifyActivityChanged() {
7653 EnumerateActivityObservers(NotifyActivityChangedCallback);
7656 bool Document::IsTopLevelWindowInactive() const {
7657 if (BrowsingContext* bc = GetBrowsingContext()) {
7658 return !bc->GetIsActiveBrowserWindow();
7661 return false;
7664 void Document::SetContainer(nsDocShell* aContainer) {
7665 if (aContainer) {
7666 mDocumentContainer = aContainer;
7667 } else {
7668 mDocumentContainer = WeakPtr<nsDocShell>();
7671 mInChromeDocShell =
7672 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7674 NotifyActivityChanged();
7676 // IsTopLevelWindowInactive depends on the docshell, so
7677 // update the cached value now that it's available.
7678 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false);
7679 if (!aContainer) {
7680 return;
7683 BrowsingContext* context = aContainer->GetBrowsingContext();
7684 if (context && context->IsContent()) {
7685 SetIsTopLevelContentDocument(context->IsTopContent());
7686 SetIsContentDocument(true);
7687 } else {
7688 SetIsTopLevelContentDocument(false);
7689 SetIsContentDocument(false);
7693 nsISupports* Document::GetContainer() const {
7694 return static_cast<nsIDocShell*>(mDocumentContainer);
7697 void Document::SetScriptGlobalObject(
7698 nsIScriptGlobalObject* aScriptGlobalObject) {
7699 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7700 mAnimationController->IsPausedByType(
7701 SMILTimeContainer::PAUSE_PAGEHIDE |
7702 SMILTimeContainer::PAUSE_BEGIN),
7703 "Clearing window pointer while animations are unpaused");
7705 if (mScriptGlobalObject && !aScriptGlobalObject) {
7706 // We're detaching from the window. We need to grab a pointer to
7707 // our layout history state now.
7708 mLayoutHistoryState = GetLayoutHistoryState();
7710 // Also make sure to remove our onload blocker now if we haven't done it yet
7711 if (mOnloadBlockCount != 0) {
7712 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7713 if (loadGroup) {
7714 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7718 if (GetController().isSome()) {
7719 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7720 loader->ClearCacheForControlledDocument(this);
7723 // We may become controlled again if this document comes back out
7724 // of bfcache. Clear our state to allow that to happen. Only
7725 // clear this flag if we are actually controlled, though, so pages
7726 // that were force reloaded don't become controlled when they
7727 // come out of bfcache.
7728 mMaybeServiceWorkerControlled = false;
7731 if (GetWindowContext()) {
7732 // The document is about to lose its window, so this is a good time to
7733 // send our page use counters, while we still have access to our
7734 // WindowContext.
7736 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7737 // catches some cases of documents losing their window that don't
7738 // get in here.)
7739 SendPageUseCounters();
7743 // BlockOnload() might be called before mScriptGlobalObject is set.
7744 // We may need to add the blocker once mScriptGlobalObject is set.
7745 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7747 mScriptGlobalObject = aScriptGlobalObject;
7749 if (needOnloadBlocker) {
7750 EnsureOnloadBlocker();
7753 UpdateFrameRequestCallbackSchedulingState();
7755 if (aScriptGlobalObject) {
7756 // Go back to using the docshell for the layout history state
7757 mLayoutHistoryState = nullptr;
7758 SetScopeObject(aScriptGlobalObject);
7759 mHasHadDefaultView = true;
7761 if (mAllowDNSPrefetch) {
7762 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7763 if (docShell) {
7764 #ifdef DEBUG
7765 nsCOMPtr<nsIWebNavigation> webNav =
7766 do_GetInterface(aScriptGlobalObject);
7767 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7768 "Unexpected container or script global?");
7769 #endif
7770 bool allowDNSPrefetch;
7771 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7772 mAllowDNSPrefetch = allowDNSPrefetch;
7776 // If we are set in a window that is already focused we should remember this
7777 // as the time the document gained focus.
7778 if (HasFocus(IgnoreErrors())) {
7779 SetLastFocusTime(TimeStamp::Now());
7783 // Remember the pointer to our window (or lack there of), to avoid
7784 // having to QI every time it's asked for.
7785 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7786 mWindow = window;
7788 // Now that we know what our window is, we can flush the CSP errors to the
7789 // Web Console. We are flushing all messages that occurred and were stored in
7790 // the queue prior to this point.
7791 if (mCSP) {
7792 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7795 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7796 do_QueryInterface(GetChannel());
7797 if (internalChannel) {
7798 nsCOMArray<nsISecurityConsoleMessage> messages;
7799 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7800 MOZ_ASSERT(NS_SUCCEEDED(rv));
7801 SendToConsole(messages);
7804 // Set our visibility state, but do not fire the event. This is correct
7805 // because either we're coming out of bfcache (in which case IsVisible() will
7806 // still test false at this point and no state change will happen) or we're
7807 // doing the initial document load and don't want to fire the event for this
7808 // change.
7810 // When the visibility is changed, notify it to observers.
7811 // Some observers need the notification, for example HTMLMediaElement uses
7812 // it to update internal media resource allocation.
7813 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7814 // creation are already done before Document::SetScriptGlobalObject() call.
7815 // MediaDecoder decides whether starting decoding is decided based on
7816 // document's visibility. When the MediaDecoder is created,
7817 // Document::SetScriptGlobalObject() is not yet called and document is
7818 // hidden state. Therefore the MediaDecoder decides that decoding is
7819 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7820 // call, the document becomes not hidden. At the time, MediaDecoder needs
7821 // to know it and needs to start updating decoding.
7822 UpdateVisibilityState(DispatchVisibilityChange::No);
7824 // The global in the template contents owner document should be the same.
7825 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7826 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7829 // Tell the script loader about the new global object.
7830 if (mScriptLoader) {
7831 mScriptLoader->SetGlobalObject(mScriptGlobalObject);
7834 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7835 mScriptGlobalObject && GetChannel()) {
7836 // If we are shift-reloaded, don't associate with a ServiceWorker.
7837 if (mDocumentContainer->IsForceReloading()) {
7838 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7839 return;
7842 mMaybeServiceWorkerControlled = true;
7846 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7847 MOZ_ASSERT(!mScriptGlobalObject,
7848 "Do not call this when mScriptGlobalObject is set!");
7849 if (mHasHadDefaultView) {
7850 return nullptr;
7853 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7854 do_QueryReferent(mScopeObject);
7855 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7856 if (win) {
7857 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7858 if (!outer || outer->GetCurrentInnerWindow() != win) {
7859 NS_WARNING("Wrong inner/outer window combination!");
7860 return nullptr;
7863 return scriptHandlingObject;
7865 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7866 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7867 "Wrong script object!");
7868 if (aScriptObject) {
7869 SetScopeObject(aScriptObject);
7870 mHasHadDefaultView = false;
7874 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7875 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7876 // Let's use mScriptGlobalObject. Even if the document is already removed from
7877 // the docshell, the outer window might be still obtainable from the it.
7878 nsCOMPtr<nsPIDOMWindowOuter> win;
7879 if (mRemovedFromDocShell) {
7880 // The docshell returns the outer window we are done.
7881 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7882 if (kungFuDeathGrip) {
7883 win = kungFuDeathGrip->GetWindow();
7885 } else {
7886 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7887 do_QueryInterface(mScriptGlobalObject)) {
7888 // mScriptGlobalObject is always the inner window, let's get the outer.
7889 win = inner->GetOuterWindow();
7893 return win;
7896 bool Document::InternalAllowXULXBL() {
7897 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7898 mAllowXULXBL = eTriTrue;
7899 return true;
7902 mAllowXULXBL = eTriFalse;
7903 return false;
7906 // Note: We don't hold a reference to the document observer; we assume
7907 // that it has a live reference to the document.
7908 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7909 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7910 "Observer already in the list");
7911 mObservers.AppendElement(aObserver);
7912 AddMutationObserver(aObserver);
7915 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7916 // If we're in the process of destroying the document (and we're
7917 // informing the observers of the destruction), don't remove the
7918 // observers from the list. This is not a big deal, since we
7919 // don't hold a live reference to the observers.
7920 if (!mInDestructor) {
7921 RemoveMutationObserver(aObserver);
7922 return mObservers.RemoveElement(aObserver);
7925 return mObservers.Contains(aObserver);
7928 void Document::BeginUpdate() {
7929 ++mUpdateNestLevel;
7930 nsContentUtils::AddScriptBlocker();
7931 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7934 void Document::EndUpdate() {
7935 const bool reset = !mPendingMaybeEditingStateChanged;
7936 mPendingMaybeEditingStateChanged = true;
7938 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7940 --mUpdateNestLevel;
7942 nsContentUtils::RemoveScriptBlocker();
7944 if (mXULBroadcastManager) {
7945 mXULBroadcastManager->MaybeBroadcast();
7948 if (reset) {
7949 mPendingMaybeEditingStateChanged = false;
7951 MaybeEditingStateChanged();
7954 void Document::BeginLoad() {
7955 if (IsEditingOn()) {
7956 // Reset() blows away all event listeners in the document, and our
7957 // editor relies heavily on those. Midas is turned on, to make it
7958 // work, re-initialize it to give it a chance to add its event
7959 // listeners again.
7961 TurnEditingOff();
7962 EditingStateChanged();
7965 MOZ_ASSERT(!mDidCallBeginLoad);
7966 mDidCallBeginLoad = true;
7968 // Block onload here to prevent having to deal with blocking and
7969 // unblocking it while we know the document is loading.
7970 BlockOnload();
7971 mDidFireDOMContentLoaded = false;
7972 BlockDOMContentLoaded();
7974 if (mScriptLoader) {
7975 mScriptLoader->BeginDeferringScripts();
7978 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
7981 void Document::MozSetImageElement(const nsAString& aImageElementId,
7982 Element* aElement) {
7983 if (aImageElementId.IsEmpty()) return;
7985 // Hold a script blocker while calling SetImageElement since that can call
7986 // out to id-observers
7987 nsAutoScriptBlocker scriptBlocker;
7989 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
7990 if (entry) {
7991 entry->SetImageElement(aElement);
7992 if (entry->IsEmpty()) {
7993 mIdentifierMap.RemoveEntry(entry);
7998 void Document::DispatchContentLoadedEvents() {
7999 // If you add early returns from this method, make sure you're
8000 // calling UnblockOnload properly.
8002 // Unpin references to preloaded images
8003 mPreloadingImages.Clear();
8005 // DOM manipulation after content loaded should not care if the element
8006 // came from the preloader.
8007 mPreloadedPreconnects.Clear();
8009 if (mTiming) {
8010 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
8013 // Dispatch observer notification to notify observers document is interactive.
8014 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
8015 if (os) {
8016 nsIPrincipal* principal = NodePrincipal();
8017 os->NotifyObservers(ToSupports(this),
8018 principal->IsSystemPrincipal()
8019 ? "chrome-document-interactive"
8020 : "content-document-interactive",
8021 nullptr);
8024 // Fire a DOM event notifying listeners that this document has been
8025 // loaded (excluding images and other loads initiated by this
8026 // document).
8027 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
8028 u"DOMContentLoaded"_ns, CanBubble::eYes,
8029 Cancelable::eNo);
8031 if (auto* const window = GetInnerWindow()) {
8032 const RefPtr<ServiceWorkerContainer> serviceWorker =
8033 window->Navigator()->ServiceWorker();
8035 // This could cause queued messages from a service worker to get
8036 // dispatched on serviceWorker.
8037 serviceWorker->StartMessages();
8040 if (MayStartLayout()) {
8041 MaybeResolveReadyForIdle();
8044 RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
8045 nsIDocShell* docShell = this->GetDocShell();
8047 if (timelines && timelines->HasConsumer(docShell)) {
8048 timelines->AddMarkerForDocShell(
8049 docShell,
8050 MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
8053 if (mTiming) {
8054 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
8057 // If this document is a [i]frame, fire a DOMFrameContentLoaded
8058 // event on all parent documents notifying that the HTML (excluding
8059 // other external files such as images and stylesheets) in a frame
8060 // has finished loading.
8062 // target_frame is the [i]frame element that will be used as the
8063 // target for the event. It's the [i]frame whose content is done
8064 // loading.
8065 nsCOMPtr<Element> target_frame = GetEmbedderElement();
8067 if (target_frame && target_frame->IsInComposedDoc()) {
8068 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
8069 while (parent) {
8070 RefPtr<Event> event;
8071 if (parent) {
8072 IgnoredErrorResult ignored;
8073 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
8076 if (event) {
8077 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
8079 event->SetTarget(target_frame);
8080 event->SetTrusted(true);
8082 // To dispatch this event we must manually call
8083 // EventDispatcher::Dispatch() on the ancestor document since the
8084 // target is not in the same document, so the event would never reach
8085 // the ancestor document if we used the normal event
8086 // dispatching code.
8088 WidgetEvent* innerEvent = event->WidgetEventPtr();
8089 if (innerEvent) {
8090 nsEventStatus status = nsEventStatus_eIgnore;
8092 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
8093 // TODO: Bug 1506441
8094 EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(parent)),
8095 context, innerEvent, event, &status);
8100 parent = parent->GetInProcessParentDocument();
8104 // If the document has a manifest attribute, fire a MozApplicationManifest
8105 // event.
8106 Element* root = GetRootElement();
8107 if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::manifest)) {
8108 nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
8109 u"MozApplicationManifest"_ns,
8110 CanBubble::eYes, Cancelable::eYes);
8113 nsPIDOMWindowInner* inner = GetInnerWindow();
8114 if (inner) {
8115 inner->NoteDOMContentLoaded();
8118 // TODO
8119 if (mMaybeServiceWorkerControlled) {
8120 using mozilla::dom::ServiceWorkerManager;
8121 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8122 if (swm) {
8123 Maybe<ClientInfo> clientInfo = GetClientInfo();
8124 if (clientInfo.isSome()) {
8125 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8130 if (mSetCompleteAfterDOMContentLoaded) {
8131 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8132 mSetCompleteAfterDOMContentLoaded = false;
8135 UnblockOnload(true);
8138 void Document::EndLoad() {
8139 bool turnOnEditing =
8140 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8142 #if defined(DEBUG)
8143 // only assert if nothing stopped the load on purpose
8144 if (!mParserAborted) {
8145 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8147 #endif
8149 // EndLoad may have been called without a matching call to BeginLoad, in the
8150 // case of a failed parse (for example, due to timeout). In such a case, we
8151 // still want to execute part of this code to do appropriate cleanup, but we
8152 // gate part of it because it is intended to match 1-for-1 with calls to
8153 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8154 // complicated and error prone to derive this condition from other related
8155 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8157 // Part 1: Code that always executes to cleanup end of parsing, whether
8158 // that parsing was successful or not.
8160 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8161 // can flush it from FlushPendingNotifications as needed. We might have to
8162 // do that to get a StartLayout() to happen.
8163 if (mParser) {
8164 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8165 mParser = nullptr;
8168 // Update the attributes on the PerformanceNavigationTiming before notifying
8169 // the onload observers.
8170 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8171 if (RefPtr<Performance> performance = window->GetPerformance()) {
8172 performance->UpdateNavigationTimingEntry();
8176 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8178 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8180 if (!mDidCallBeginLoad) {
8181 return;
8183 mDidCallBeginLoad = false;
8185 UnblockDOMContentLoaded();
8187 if (turnOnEditing) {
8188 EditingStateChanged();
8191 if (!GetWindow()) {
8192 // This is a document that's not in a window. For example, this could be an
8193 // XMLHttpRequest responseXML document, or a document created via DOMParser
8194 // or DOMImplementation. We don't reach this code normally for such
8195 // documents (which is not obviously correct), but can reach it via
8196 // document.open()/document.close().
8198 // Such documents don't fire load events, but per spec should set their
8199 // readyState to "complete" when parsing and all loading of subresources is
8200 // done. Parsing is done now, and documents not in a window don't load
8201 // subresources, so just go ahead and mark ourselves as complete.
8202 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8203 /* updateTimingInformation = */ false);
8205 // Reset mSkipLoadEventAfterClose just in case.
8206 mSkipLoadEventAfterClose = false;
8210 void Document::UnblockDOMContentLoaded() {
8211 MOZ_ASSERT(mBlockDOMContentLoaded);
8212 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8213 return;
8216 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8217 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8219 mDidFireDOMContentLoaded = true;
8220 if (PresShell* presShell = GetPresShell()) {
8221 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
8224 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8225 if (!mSynchronousDOMContentLoaded) {
8226 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8227 nsCOMPtr<nsIRunnable> ev =
8228 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8229 &Document::DispatchContentLoadedEvents);
8230 Dispatch(TaskCategory::Other, ev.forget());
8231 } else {
8232 DispatchContentLoadedEvents();
8236 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) {
8237 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8238 "Someone forgot a scriptblocker");
8239 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged,
8240 (this, aElement, aStateMask));
8243 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8244 StyleRuleChangeKind) {
8245 if (aSheet.IsApplicable()) {
8246 ApplicableStylesChanged();
8250 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8251 if (aRule.IsIncompleteImportRule()) {
8252 return;
8255 if (aSheet.IsApplicable()) {
8256 ApplicableStylesChanged();
8260 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8261 if (aSheet.IsApplicable()) {
8262 ApplicableStylesChanged();
8266 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8267 if (aSheet.IsApplicable()) {
8268 ApplicableStylesChanged();
8272 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8273 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8274 return nullptr;
8277 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8280 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8281 Element& aElement, bool aForce, ErrorResult& aRv) {
8282 // Clone the node to avoid returning a direct reference.
8283 nsCOMPtr<nsINode> clone = aElement.CloneNode(true, aRv);
8284 if (aRv.Failed()) {
8285 return nullptr;
8288 PresShell* shell = GetPresShell();
8289 if (aForce && !GetCustomContentContainer(shell)) {
8290 FlushPendingNotifications(FlushType::Layout);
8291 shell = GetPresShell();
8294 nsAutoScriptBlocker scriptBlocker;
8296 auto anonContent =
8297 MakeRefPtr<AnonymousContent>(clone.forget().downcast<Element>());
8299 mAnonymousContents.AppendElement(anonContent);
8301 if (Element* container = GetCustomContentContainer(shell)) {
8302 container->AppendChildTo(&anonContent->ContentNode(), true, IgnoreErrors());
8303 shell->GetCanvasFrame()->ShowCustomContentContainer();
8306 return anonContent.forget();
8309 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8310 PresShell* aPresShell) {
8311 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8312 if (!container) {
8313 return;
8315 container->RemoveChild(aAnonContent.ContentNode(), IgnoreErrors());
8318 void Document::RemoveAnonymousContent(AnonymousContent& aContent,
8319 ErrorResult& aRv) {
8320 nsAutoScriptBlocker scriptBlocker;
8322 auto index = mAnonymousContents.IndexOf(&aContent);
8323 if (index == mAnonymousContents.NoIndex) {
8324 return;
8327 mAnonymousContents.RemoveElementAt(index);
8328 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8330 if (mAnonymousContents.IsEmpty() &&
8331 GetCustomContentContainer(GetPresShell())) {
8332 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8336 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8337 nsINode* aNode) const {
8338 if (!aNode->IsInNativeAnonymousSubtree()) {
8339 return nullptr;
8342 PresShell* presShell = GetPresShell();
8343 if (!presShell || !presShell->GetCanvasFrame()) {
8344 return nullptr;
8347 nsAutoScriptBlocker scriptBlocker;
8348 nsCOMPtr<Element> customContainer =
8349 presShell->GetCanvasFrame()->GetCustomContentContainer();
8350 if (!customContainer) {
8351 return nullptr;
8354 // An arbitrary number of elements can be inserted as children of the custom
8355 // container frame. We want the one that was added that contains aNode, so
8356 // we need to keep track of the last child separately using |child| here.
8357 nsINode* child = aNode;
8358 nsINode* parent = aNode->GetParentNode();
8359 while (parent && parent->IsInNativeAnonymousSubtree()) {
8360 if (parent == customContainer) {
8361 return Element::FromNode(child);
8363 child = parent;
8364 parent = child->GetParentNode();
8366 return nullptr;
8369 Maybe<ClientInfo> Document::GetClientInfo() const {
8370 if (const Document* orig = GetOriginalDocument()) {
8371 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8372 return info;
8376 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8377 return inner->GetClientInfo();
8380 return Maybe<ClientInfo>();
8383 Maybe<ClientState> Document::GetClientState() const {
8384 if (const Document* orig = GetOriginalDocument()) {
8385 if (Maybe<ClientState> state = orig->GetClientState()) {
8386 return state;
8390 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8391 return inner->GetClientState();
8394 return Maybe<ClientState>();
8397 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8398 if (const Document* orig = GetOriginalDocument()) {
8399 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8400 return controller;
8404 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8405 return inner->GetController();
8408 return Maybe<ServiceWorkerDescriptor>();
8412 // Document interface
8414 DocumentType* Document::GetDoctype() const {
8415 for (nsIContent* child = GetFirstChild(); child;
8416 child = child->GetNextSibling()) {
8417 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8418 return static_cast<DocumentType*>(child);
8421 return nullptr;
8424 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8425 if (!mDOMImplementation) {
8426 nsCOMPtr<nsIURI> uri;
8427 NS_NewURI(getter_AddRefs(uri), "about:blank");
8428 if (!uri) {
8429 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8430 return nullptr;
8432 bool hasHadScriptObject = true;
8433 nsIScriptGlobalObject* scriptObject =
8434 GetScriptHandlingObject(hasHadScriptObject);
8435 if (!scriptObject && hasHadScriptObject) {
8436 rv.Throw(NS_ERROR_UNEXPECTED);
8437 return nullptr;
8439 mDOMImplementation = new DOMImplementation(
8440 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8443 return mDOMImplementation;
8446 bool IsLowercaseASCII(const nsAString& aValue) {
8447 int32_t len = aValue.Length();
8448 for (int32_t i = 0; i < len; ++i) {
8449 char16_t c = aValue[i];
8450 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8451 return false;
8454 return true;
8457 already_AddRefed<Element> Document::CreateElement(
8458 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8459 ErrorResult& rv) {
8460 rv = nsContentUtils::CheckQName(aTagName, false);
8461 if (rv.Failed()) {
8462 return nullptr;
8465 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8466 nsAutoString lcTagName;
8467 if (needsLowercase) {
8468 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8471 const nsString* is = nullptr;
8472 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8473 if (aOptions.IsElementCreationOptions()) {
8474 const ElementCreationOptions& options =
8475 aOptions.GetAsElementCreationOptions();
8477 if (options.mIs.WasPassed()) {
8478 is = &options.mIs.Value();
8481 // Check 'pseudo' and throw an exception if it's not one allowed
8482 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8483 if (options.mPseudo.WasPassed()) {
8484 Maybe<PseudoStyleType> type =
8485 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8486 if (!type || *type == PseudoStyleType::NotPseudo ||
8487 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8488 rv.ThrowNotSupportedError("Invalid pseudo-element");
8489 return nullptr;
8491 pseudoType = *type;
8495 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8496 nullptr, mDefaultElementType, is);
8498 if (pseudoType != PseudoStyleType::NotPseudo) {
8499 elem->SetPseudoElementType(pseudoType);
8502 return elem.forget();
8505 already_AddRefed<Element> Document::CreateElementNS(
8506 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8507 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8508 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8509 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8510 mNodeInfoManager, ELEMENT_NODE,
8511 getter_AddRefs(nodeInfo));
8512 if (rv.Failed()) {
8513 return nullptr;
8516 const nsString* is = nullptr;
8517 if (aOptions.IsElementCreationOptions()) {
8518 const ElementCreationOptions& options =
8519 aOptions.GetAsElementCreationOptions();
8520 if (options.mIs.WasPassed()) {
8521 is = &options.mIs.Value();
8525 nsCOMPtr<Element> element;
8526 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8527 NOT_FROM_PARSER, is);
8528 if (rv.Failed()) {
8529 return nullptr;
8532 return element.forget();
8535 already_AddRefed<Element> Document::CreateXULElement(
8536 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8537 ErrorResult& aRv) {
8538 aRv = nsContentUtils::CheckQName(aTagName, false);
8539 if (aRv.Failed()) {
8540 return nullptr;
8543 const nsString* is = nullptr;
8544 if (aOptions.IsElementCreationOptions()) {
8545 const ElementCreationOptions& options =
8546 aOptions.GetAsElementCreationOptions();
8547 if (options.mIs.WasPassed()) {
8548 is = &options.mIs.Value();
8552 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8553 if (!elem) {
8554 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8555 return nullptr;
8557 return elem.forget();
8560 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8561 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8562 return text.forget();
8565 already_AddRefed<nsTextNode> Document::CreateTextNode(
8566 const nsAString& aData) const {
8567 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8568 // Don't notify; this node is still being created.
8569 text->SetText(aData, false);
8570 return text.forget();
8573 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8574 RefPtr<DocumentFragment> frag =
8575 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8576 return frag.forget();
8579 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8580 already_AddRefed<dom::Comment> Document::CreateComment(
8581 const nsAString& aData) const {
8582 RefPtr<dom::Comment> comment =
8583 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8585 // Don't notify; this node is still being created.
8586 comment->SetText(aData, false);
8587 return comment.forget();
8590 already_AddRefed<CDATASection> Document::CreateCDATASection(
8591 const nsAString& aData, ErrorResult& rv) {
8592 if (IsHTMLDocument()) {
8593 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8594 return nullptr;
8597 if (FindInReadable(u"]]>"_ns, aData)) {
8598 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8599 return nullptr;
8602 RefPtr<CDATASection> cdata =
8603 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8605 // Don't notify; this node is still being created.
8606 cdata->SetText(aData, false);
8608 return cdata.forget();
8611 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8612 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8613 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8614 if (NS_FAILED(res)) {
8615 rv.Throw(res);
8616 return nullptr;
8619 if (FindInReadable(u"?>"_ns, aData)) {
8620 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8621 return nullptr;
8624 RefPtr<ProcessingInstruction> pi =
8625 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8627 return pi.forget();
8630 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8631 ErrorResult& rv) {
8632 if (!mNodeInfoManager) {
8633 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8634 return nullptr;
8637 nsresult res = nsContentUtils::CheckQName(aName, false);
8638 if (NS_FAILED(res)) {
8639 rv.Throw(res);
8640 return nullptr;
8643 nsAutoString name;
8644 if (IsHTMLDocument()) {
8645 nsContentUtils::ASCIIToLower(aName, name);
8646 } else {
8647 name = aName;
8650 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8651 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8652 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8653 if (NS_FAILED(res)) {
8654 rv.Throw(res);
8655 return nullptr;
8658 RefPtr<Attr> attribute =
8659 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8660 return attribute.forget();
8663 already_AddRefed<Attr> Document::CreateAttributeNS(
8664 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8665 ErrorResult& rv) {
8666 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8667 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8668 mNodeInfoManager, ATTRIBUTE_NODE,
8669 getter_AddRefs(nodeInfo));
8670 if (rv.Failed()) {
8671 return nullptr;
8674 RefPtr<Attr> attribute =
8675 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8676 return attribute.forget();
8679 void Document::ResolveScheduledSVGPresAttrs() {
8680 for (SVGElement* svg : mLazySVGPresElements) {
8681 svg->UpdateContentDeclarationBlock();
8683 mLazySVGPresElements.Clear();
8686 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8687 const {
8688 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8690 const nsTArray<nsWeakPtr> blockedNodes = mBlockedNodesByClassifier.Clone();
8692 for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
8693 nsWeakPtr weakNode = blockedNodes[i];
8694 nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
8695 // Consider only nodes to which we have managed to get strong references.
8696 // Coping with nullptrs since it's expected for nodes to disappear when
8697 // nobody else is referring to them.
8698 if (node) {
8699 list->AppendElement(node);
8703 return list.forget();
8706 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8707 aSheetSet.Truncate();
8709 // Look through our sheets, find the selected set title
8710 size_t count = SheetCount();
8711 nsAutoString title;
8712 for (size_t index = 0; index < count; index++) {
8713 StyleSheet* sheet = SheetAt(index);
8714 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8716 if (sheet->Disabled()) {
8717 // Disabled sheets don't affect the currently selected set
8718 continue;
8721 sheet->GetTitle(title);
8723 if (aSheetSet.IsEmpty()) {
8724 aSheetSet = title;
8725 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8726 // Sheets from multiple sets enabled; return null string, per spec.
8727 SetDOMStringToNull(aSheetSet);
8728 return;
8733 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8734 if (DOMStringIsNull(aSheetSet)) {
8735 return;
8738 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8739 // or CSSLoaders.
8740 mLastStyleSheetSet = aSheetSet;
8741 EnableStyleSheetsForSetInternal(aSheetSet, true);
8744 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8745 mPreferredStyleSheetSet = aSheetSet;
8746 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8747 // spec.
8748 if (DOMStringIsNull(mLastStyleSheetSet)) {
8749 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8750 // per spec. The idea here is that we're changing our preferred set and
8751 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8752 // using the Internal version so we can update the CSSLoader and not have
8753 // to worry about null strings.
8754 EnableStyleSheetsForSetInternal(aSheetSet, true);
8758 DOMStringList* Document::StyleSheetSets() {
8759 if (!mStyleSheetSetList) {
8760 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8762 return mStyleSheetSetList;
8765 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8766 // Per spec, passing in null is a no-op.
8767 if (!DOMStringIsNull(aSheetSet)) {
8768 // Note: must make sure to not change the CSSLoader's preferred sheet --
8769 // that value should be equal to either our lastStyleSheetSet (if that's
8770 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8771 // change either of those.
8772 EnableStyleSheetsForSetInternal(aSheetSet, false);
8776 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8777 bool aUpdateCSSLoader) {
8778 size_t count = SheetCount();
8779 nsAutoString title;
8780 for (size_t index = 0; index < count; index++) {
8781 StyleSheet* sheet = SheetAt(index);
8782 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8784 sheet->GetTitle(title);
8785 if (!title.IsEmpty()) {
8786 sheet->SetEnabled(title.Equals(aSheetSet));
8789 if (aUpdateCSSLoader) {
8790 CSSLoader()->DocumentStyleSheetSetChanged();
8792 if (mStyleSet->StyleSheetsHaveChanged()) {
8793 ApplicableStylesChanged();
8797 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8798 nsAutoCString charset;
8799 GetDocumentCharacterSet()->Name(charset);
8800 CopyASCIItoUTF16(charset, aCharacterSet);
8803 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8804 ErrorResult& rv) const {
8805 nsINode* imported = &aNode;
8807 switch (imported->NodeType()) {
8808 case DOCUMENT_NODE: {
8809 break;
8811 case DOCUMENT_FRAGMENT_NODE:
8812 case ATTRIBUTE_NODE:
8813 case ELEMENT_NODE:
8814 case PROCESSING_INSTRUCTION_NODE:
8815 case TEXT_NODE:
8816 case CDATA_SECTION_NODE:
8817 case COMMENT_NODE:
8818 case DOCUMENT_TYPE_NODE: {
8819 return imported->Clone(aDeep, mNodeInfoManager, rv);
8821 default: {
8822 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8826 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8827 return nullptr;
8830 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8831 return nsRange::Create(this, 0, this, 0, rv);
8834 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8835 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8836 ErrorResult& rv) const {
8837 RefPtr<NodeIterator> iterator =
8838 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8839 return iterator.forget();
8842 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8843 uint32_t aWhatToShow,
8844 NodeFilter* aFilter,
8845 ErrorResult& rv) const {
8846 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8847 return walker.forget();
8850 already_AddRefed<Location> Document::GetLocation() const {
8851 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8853 if (!w) {
8854 return nullptr;
8857 return do_AddRef(w->Location());
8860 already_AddRefed<nsIURI> Document::GetDomainURI() {
8861 nsIPrincipal* principal = NodePrincipal();
8863 nsCOMPtr<nsIURI> uri;
8864 principal->GetDomain(getter_AddRefs(uri));
8865 if (uri) {
8866 return uri.forget();
8868 auto* basePrin = BasePrincipal::Cast(principal);
8869 basePrin->GetURI(getter_AddRefs(uri));
8870 return uri.forget();
8873 void Document::GetDomain(nsAString& aDomain) {
8874 nsCOMPtr<nsIURI> uri = GetDomainURI();
8876 if (!uri) {
8877 aDomain.Truncate();
8878 return;
8881 nsAutoCString hostName;
8882 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8883 if (NS_SUCCEEDED(rv)) {
8884 CopyUTF8toUTF16(hostName, aDomain);
8885 } else {
8886 // If we can't get the host from the URI (e.g. about:, javascript:,
8887 // etc), just return an empty string.
8888 aDomain.Truncate();
8892 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8893 if (!GetBrowsingContext()) {
8894 // If our browsing context is null; disallow setting domain
8895 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8896 return;
8899 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8900 // We're sandboxed; disallow setting domain
8901 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8902 return;
8905 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8906 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8907 return;
8910 if (aDomain.IsEmpty()) {
8911 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8912 return;
8915 nsCOMPtr<nsIURI> uri = GetDomainURI();
8916 if (!uri) {
8917 rv.Throw(NS_ERROR_FAILURE);
8918 return;
8921 // Check new domain - must be a superdomain of the current host
8922 // For example, a page from foo.bar.com may set domain to bar.com,
8923 // but not to ar.com, baz.com, or fi.foo.bar.com.
8925 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8926 if (!newURI) {
8927 // Error: illegal domain
8928 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8929 return;
8932 if (CrossOriginIsolated()) {
8933 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8934 return;
8937 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
8938 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
8939 WindowGlobalChild* wgc = GetWindowGlobalChild();
8940 if (wgc) {
8941 wgc->SendSetDocumentDomain(newURI);
8945 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
8946 const nsACString& aHostString) {
8947 if (aHostString.IsEmpty()) {
8948 return nullptr;
8951 // Create new URI
8952 nsCOMPtr<nsIURI> uri = GetDomainURI();
8953 if (!uri) {
8954 return nullptr;
8957 nsresult rv;
8958 rv = NS_MutateURI(uri)
8959 .SetUserPass(""_ns)
8960 .SetPort(-1) // we want to reset the port number if needed.
8961 .SetHostPort(aHostString)
8962 .Finalize(uri);
8963 if (NS_FAILED(rv)) {
8964 return nullptr;
8967 return uri.forget();
8970 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
8971 const nsAString& aNewDomain, nsIURI* aOrigHost) {
8972 if (NS_WARN_IF(!aOrigHost)) {
8973 return nullptr;
8976 nsCOMPtr<nsIURI> newURI =
8977 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
8978 if (!newURI) {
8979 // Error: failed to parse input domain
8980 return nullptr;
8983 if (!IsValidDomain(aOrigHost, newURI)) {
8984 // Error: illegal domain
8985 return nullptr;
8988 nsAutoCString domain;
8989 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
8990 return nullptr;
8993 return CreateInheritingURIForHost(domain);
8996 /* static */
8997 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
8998 // Check new domain - must be a superdomain of the current host
8999 // For example, a page from foo.bar.com may set domain to bar.com,
9000 // but not to ar.com, baz.com, or fi.foo.bar.com.
9001 nsAutoCString current;
9002 nsAutoCString domain;
9003 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
9004 current.Truncate();
9006 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
9007 domain.Truncate();
9010 bool ok = current.Equals(domain);
9011 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
9012 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
9013 // We're golden if the new domain is the current page's base domain or a
9014 // subdomain of it.
9015 nsCOMPtr<nsIEffectiveTLDService> tldService =
9016 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
9017 if (!tldService) {
9018 return false;
9021 nsAutoCString currentBaseDomain;
9022 ok = NS_SUCCEEDED(
9023 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
9024 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
9025 (domain.Length() >= currentBaseDomain.Length()),
9026 "uh-oh! slight optimization wasn't valid somehow!");
9027 ok = ok && domain.Length() >= currentBaseDomain.Length();
9030 return ok;
9033 Element* Document::GetHtmlElement() const {
9034 Element* rootElement = GetRootElement();
9035 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
9036 return rootElement;
9037 return nullptr;
9040 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
9041 Element* html = GetHtmlElement();
9042 if (!html) return nullptr;
9044 // Look for the element with aTag inside html. This needs to run
9045 // forwards to find the first such element.
9046 for (nsIContent* child = html->GetFirstChild(); child;
9047 child = child->GetNextSibling()) {
9048 if (child->IsHTMLElement(aTag)) return child->AsElement();
9050 return nullptr;
9053 nsGenericHTMLElement* Document::GetBody() {
9054 Element* html = GetHtmlElement();
9055 if (!html) {
9056 return nullptr;
9059 for (nsIContent* child = html->GetFirstChild(); child;
9060 child = child->GetNextSibling()) {
9061 if (child->IsHTMLElement(nsGkAtoms::body) ||
9062 child->IsHTMLElement(nsGkAtoms::frameset)) {
9063 return static_cast<nsGenericHTMLElement*>(child);
9067 return nullptr;
9070 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
9071 nsCOMPtr<Element> root = GetRootElement();
9073 // The body element must be either a body tag or a frameset tag. And we must
9074 // have a root element to be able to add kids to it.
9075 if (!newBody ||
9076 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
9077 rv.ThrowHierarchyRequestError(
9078 "The new body must be either a body tag or frameset tag.");
9079 return;
9082 if (!root) {
9083 rv.ThrowHierarchyRequestError("No root element.");
9084 return;
9087 // Use DOM methods so that we pass through the appropriate security checks.
9088 nsCOMPtr<Element> currentBody = GetBody();
9089 if (currentBody) {
9090 root->ReplaceChild(*newBody, *currentBody, rv);
9091 } else {
9092 root->AppendChild(*newBody, rv);
9096 HTMLSharedElement* Document::GetHead() {
9097 return static_cast<HTMLSharedElement*>(GetHeadElement());
9100 Element* Document::GetTitleElement() {
9101 // mMayHaveTitleElement will have been set to true if any HTML or SVG
9102 // <title> element has been bound to this document. So if it's false,
9103 // we know there is nothing to do here. This avoids us having to search
9104 // the whole DOM if someone calls document.title on a large document
9105 // without a title.
9106 if (!mMayHaveTitleElement) return nullptr;
9108 Element* root = GetRootElement();
9109 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
9110 // In SVG, the document's title must be a child
9111 for (nsIContent* child = root->GetFirstChild(); child;
9112 child = child->GetNextSibling()) {
9113 if (child->IsSVGElement(nsGkAtoms::title)) {
9114 return child->AsElement();
9117 return nullptr;
9120 // We check the HTML namespace even for non-HTML documents, except SVG. This
9121 // matches the spec and the behavior of all tested browsers.
9122 // We avoid creating a live nsContentList since we don't need to watch for DOM
9123 // tree mutations.
9124 RefPtr<nsContentList> list = new nsContentList(
9125 this, kNameSpaceID_XHTML, nsGkAtoms::title, nsGkAtoms::title,
9126 /* aDeep = */ true,
9127 /* aLiveList = */ false);
9129 nsIContent* first = list->Item(0, false);
9131 return first ? first->AsElement() : nullptr;
9134 void Document::GetTitle(nsAString& aTitle) {
9135 aTitle.Truncate();
9137 Element* rootElement = GetRootElement();
9138 if (!rootElement) {
9139 return;
9142 nsAutoString tmp;
9144 if (rootElement->IsXULElement()) {
9145 rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp);
9146 } else {
9147 Element* title = GetTitleElement();
9148 if (!title) {
9149 return;
9151 nsContentUtils::GetNodeTextContent(title, false, tmp);
9154 tmp.CompressWhitespace();
9155 aTitle = tmp;
9158 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9159 Element* rootElement = GetRootElement();
9160 if (!rootElement) {
9161 return;
9164 if (rootElement->IsXULElement()) {
9165 aRv =
9166 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9167 return;
9170 Maybe<mozAutoDocUpdate> updateBatch;
9171 nsCOMPtr<Element> title = GetTitleElement();
9172 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9173 if (!title) {
9174 // Batch updates so that mutation events don't change "the title
9175 // element" under us
9176 updateBatch.emplace(this, true);
9177 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9178 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9179 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9180 NOT_FROM_PARSER);
9181 if (!title) {
9182 return;
9184 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9185 IgnoreErrors());
9187 } else if (rootElement->IsHTMLElement()) {
9188 if (!title) {
9189 // Batch updates so that mutation events don't change "the title
9190 // element" under us
9191 updateBatch.emplace(this, true);
9192 Element* head = GetHeadElement();
9193 if (!head) {
9194 return;
9197 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9198 titleInfo = mNodeInfoManager->GetNodeInfo(
9199 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9200 title = NS_NewHTMLTitleElement(titleInfo.forget());
9201 if (!title) {
9202 return;
9205 head->AppendChildTo(title, true, IgnoreErrors());
9207 } else {
9208 return;
9211 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9214 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9215 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9216 "Setting a title while unlinking or destroying the element?");
9217 if (mInUnlinkOrDeletion) {
9218 return;
9221 if (aBoundTitleElement) {
9222 mMayHaveTitleElement = true;
9224 if (mPendingTitleChangeEvent.IsPending()) return;
9226 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9227 RefPtr<nsRunnableMethod<Document, void, false>> event =
9228 NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
9229 &Document::DoNotifyPossibleTitleChange);
9230 nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(event));
9231 if (NS_SUCCEEDED(rv)) {
9232 mPendingTitleChangeEvent = std::move(event);
9236 void Document::DoNotifyPossibleTitleChange() {
9237 if (!mPendingTitleChangeEvent.IsPending()) {
9238 return;
9240 // Make sure the pending runnable method is cleared.
9241 mPendingTitleChangeEvent.Revoke();
9242 mHaveFiredTitleChange = true;
9244 nsAutoString title;
9245 GetTitle(title);
9247 RefPtr<PresShell> presShell = GetPresShell();
9248 if (presShell) {
9249 nsCOMPtr<nsISupports> container =
9250 presShell->GetPresContext()->GetContainerWeak();
9251 if (container) {
9252 nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container);
9253 if (docShellWin) {
9254 docShellWin->SetTitle(title);
9259 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
9260 if (WindowGlobalChild* child = inner->GetWindowGlobalChild()) {
9261 child->SendUpdateDocumentTitle(title);
9265 // Fire a DOM event for the title change.
9266 nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
9267 u"DOMTitleChanged"_ns, CanBubble::eYes,
9268 Cancelable::eYes);
9270 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9271 if (obs) {
9272 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9276 already_AddRefed<MediaQueryList> Document::MatchMedia(
9277 const nsACString& aMediaQueryList, CallerType aCallerType) {
9278 RefPtr<MediaQueryList> result =
9279 new MediaQueryList(this, aMediaQueryList, aCallerType);
9281 mDOMMediaQueryLists.insertBack(result);
9283 return result.forget();
9286 void Document::SetMayStartLayout(bool aMayStartLayout) {
9287 mMayStartLayout = aMayStartLayout;
9288 if (MayStartLayout()) {
9289 // Before starting layout, check whether we're a toplevel chrome
9290 // window. If we are, setup some state so that we don't have to restyle
9291 // the whole tree after StartLayout.
9292 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9293 // We're the chrome document!
9294 win->BeforeStartLayout();
9296 ReadyState state = GetReadyStateEnum();
9297 if (state >= READYSTATE_INTERACTIVE) {
9298 // DOMContentLoaded has fired already.
9299 MaybeResolveReadyForIdle();
9303 MaybeEditingStateChanged();
9306 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9307 mInitializableFrameLoaders.RemoveElement(aLoader);
9308 // Don't even try to initialize.
9309 if (mInDestructor) {
9310 NS_WARNING(
9311 "Trying to initialize a frame loader while"
9312 "document is being deleted");
9313 return NS_ERROR_FAILURE;
9316 mInitializableFrameLoaders.AppendElement(aLoader);
9317 if (!mFrameLoaderRunner) {
9318 mFrameLoaderRunner =
9319 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9320 &Document::MaybeInitializeFinalizeFrameLoaders);
9321 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9322 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9324 return NS_OK;
9327 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9328 nsIRunnable* aFinalizer) {
9329 mInitializableFrameLoaders.RemoveElement(aLoader);
9330 if (mInDestructor) {
9331 return NS_ERROR_FAILURE;
9334 LogRunnable::LogDispatch(aFinalizer);
9335 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9336 if (!mFrameLoaderRunner) {
9337 mFrameLoaderRunner =
9338 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9339 &Document::MaybeInitializeFinalizeFrameLoaders);
9340 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9341 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9343 return NS_OK;
9346 void Document::MaybeInitializeFinalizeFrameLoaders() {
9347 if (mDelayFrameLoaderInitialization) {
9348 // This method will be recalled when !mDelayFrameLoaderInitialization.
9349 mFrameLoaderRunner = nullptr;
9350 return;
9353 // We're not in an update, but it is not safe to run scripts, so
9354 // postpone frameloader initialization and finalization.
9355 if (!nsContentUtils::IsSafeToRunScript()) {
9356 if (!mInDestructor && !mFrameLoaderRunner &&
9357 (mInitializableFrameLoaders.Length() ||
9358 mFrameLoaderFinalizers.Length())) {
9359 mFrameLoaderRunner = NewRunnableMethod(
9360 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9361 &Document::MaybeInitializeFinalizeFrameLoaders);
9362 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9364 return;
9366 mFrameLoaderRunner = nullptr;
9368 // Don't use a temporary array for mInitializableFrameLoaders, because
9369 // loading a frame may cause some other frameloader to be removed from the
9370 // array. But be careful to keep the loader alive when starting the load!
9371 while (mInitializableFrameLoaders.Length()) {
9372 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9373 mInitializableFrameLoaders.RemoveElementAt(0);
9374 NS_ASSERTION(loader, "null frameloader in the array?");
9375 loader->ReallyStartLoading();
9378 uint32_t length = mFrameLoaderFinalizers.Length();
9379 if (length > 0) {
9380 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9381 std::move(mFrameLoaderFinalizers);
9382 for (uint32_t i = 0; i < length; ++i) {
9383 LogRunnable::Run run(finalizers[i]);
9384 finalizers[i]->Run();
9389 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9390 uint32_t length = mInitializableFrameLoaders.Length();
9391 for (uint32_t i = 0; i < length; ++i) {
9392 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9393 mInitializableFrameLoaders.RemoveElementAt(i);
9394 return;
9399 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9400 mPrototypeDocument = aPrototype;
9401 mSynchronousDOMContentLoaded = true;
9404 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9405 return GetPermissionDelegateHandler();
9408 Document* Document::RequestExternalResource(
9409 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9410 ExternalResourceLoad** aPendingLoad) {
9411 MOZ_ASSERT(aURI, "Must have a URI");
9412 MOZ_ASSERT(aRequestingNode, "Must have a node");
9413 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9414 if (mDisplayDocument) {
9415 return mDisplayDocument->RequestExternalResource(
9416 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9419 return mExternalResourceMap.RequestResource(
9420 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9423 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9424 mExternalResourceMap.EnumerateResources(aCallback);
9427 SMILAnimationController* Document::GetAnimationController() {
9428 // We create the animation controller lazily because most documents won't want
9429 // one and only SVG documents and the like will call this
9430 if (mAnimationController) return mAnimationController;
9431 // Refuse to create an Animation Controller for data documents.
9432 if (mLoadedAsData) return nullptr;
9434 mAnimationController = new SMILAnimationController(this);
9436 // If there's a presContext then check the animation mode and pause if
9437 // necessary.
9438 nsPresContext* context = GetPresContext();
9439 if (mAnimationController && context &&
9440 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9441 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9444 // If we're hidden (or being hidden), notify the newly-created animation
9445 // controller. (Skip this check for SVG-as-an-image documents, though,
9446 // because they don't get OnPageShow / OnPageHide calls).
9447 if (!mIsShowing && !mIsBeingUsedAsImage) {
9448 mAnimationController->OnPageHide();
9451 return mAnimationController;
9454 PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
9455 if (!mPendingAnimationTracker) {
9456 mPendingAnimationTracker = new PendingAnimationTracker(this);
9459 return mPendingAnimationTracker;
9463 * Retrieve the "direction" property of the document.
9465 * @lina 01/09/2001
9467 void Document::GetDir(nsAString& aDirection) const {
9468 aDirection.Truncate();
9469 Element* rootElement = GetHtmlElement();
9470 if (rootElement) {
9471 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9476 * Set the "direction" property of the document.
9478 * @lina 01/09/2001
9480 void Document::SetDir(const nsAString& aDirection) {
9481 Element* rootElement = GetHtmlElement();
9482 if (rootElement) {
9483 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9487 nsIHTMLCollection* Document::Images() {
9488 if (!mImages) {
9489 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9490 nsGkAtoms::img);
9492 return mImages;
9495 nsIHTMLCollection* Document::Embeds() {
9496 if (!mEmbeds) {
9497 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9498 nsGkAtoms::embed);
9500 return mEmbeds;
9503 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9504 void* aData) {
9505 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9506 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
9509 nsIHTMLCollection* Document::Links() {
9510 if (!mLinks) {
9511 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9513 return mLinks;
9516 nsIHTMLCollection* Document::Forms() {
9517 if (!mForms) {
9518 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9519 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9520 nsGkAtoms::form);
9523 return mForms;
9526 nsIHTMLCollection* Document::Scripts() {
9527 if (!mScripts) {
9528 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9529 nsGkAtoms::script);
9531 return mScripts;
9534 nsIHTMLCollection* Document::Applets() {
9535 if (!mApplets) {
9536 mApplets = new nsEmptyContentList(this);
9538 return mApplets;
9541 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9542 void* aData) {
9543 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9544 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
9547 nsIHTMLCollection* Document::Anchors() {
9548 if (!mAnchors) {
9549 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9551 return mAnchors;
9554 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9555 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9556 ErrorResult& rv) {
9557 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9558 "XOW should have caught this!");
9560 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9561 if (!window) {
9562 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9563 return nullptr;
9565 nsCOMPtr<nsPIDOMWindowOuter> outer =
9566 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9567 if (!outer) {
9568 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9569 return nullptr;
9571 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9572 RefPtr<BrowsingContext> newBC;
9573 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9574 if (!newBC) {
9575 return nullptr;
9577 return WindowProxyHolder(std::move(newBC));
9580 Document* Document::Open(const Optional<nsAString>& /* unused */,
9581 const Optional<nsAString>& /* unused */,
9582 ErrorResult& aError) {
9583 // Implements
9584 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9586 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9587 "XOW should have caught this!");
9589 // Step 1 -- throw if we're an XML document.
9590 if (!IsHTMLDocument() || mDisableDocWrite) {
9591 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9592 return nullptr;
9595 // Step 2 -- throw if dynamic markup insertion should throw.
9596 if (ShouldThrowOnDynamicMarkupInsertion()) {
9597 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9598 return nullptr;
9601 // Step 3 -- get the entry document, so we can use it for security checks.
9602 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9603 if (!callerDoc) {
9604 // If we're called from C++ or in some other way without an originating
9605 // document we can't do a document.open w/o changing the principal of the
9606 // document to something like about:blank (as that's the only sane thing to
9607 // do when we don't know the origin of this call), and since we can't
9608 // change the principals of a document for security reasons we'll have to
9609 // refuse to go ahead with this call.
9611 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9612 return nullptr;
9615 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9616 // the entry document.
9617 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9618 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9619 return nullptr;
9622 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9623 // just no-op.
9624 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9625 return this;
9628 // Step 6 -- check for open() during unload. Per spec, this is just a check
9629 // of the ignore-opens-during-unload counter, but our unload event code
9630 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9631 // do), so we check for unload directly.
9632 if (ShouldIgnoreOpens()) {
9633 return this;
9636 RefPtr<nsDocShell> shell(mDocumentContainer);
9637 if (shell) {
9638 bool inUnload;
9639 shell->GetIsInUnload(&inUnload);
9640 if (inUnload) {
9641 return this;
9645 // document.open() inherits the CSP from the opening document.
9646 // Please create an actual copy of the CSP (do not share the same
9647 // reference) otherwise appending a new policy within the opened
9648 // document will be incorrectly propagated to the opening doc.
9649 nsCOMPtr<nsIContentSecurityPolicy> csp = callerDoc->GetCsp();
9650 if (csp) {
9651 RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
9652 cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
9653 mCSP = cspToInherit;
9656 // At this point we know this is a valid-enough document.open() call
9657 // and not a no-op. Increment our use counter.
9658 SetUseCounter(eUseCounter_custom_DocumentOpen);
9660 // Step 7 -- stop existing navigation of our browsing context (and all other
9661 // loads it's doing) if we're the active document of our browsing context.
9662 // Note that we do not want to stop anything if there is no existing
9663 // navigation.
9664 if (shell && IsCurrentActiveDocument() &&
9665 shell->GetIsAttemptingToNavigate()) {
9666 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9668 // The Stop call may have cancelled the onload blocker request or
9669 // prevented it from getting added, so we need to make sure it gets added
9670 // to the document again otherwise the document could have a non-zero
9671 // onload block count without the onload blocker request being in the
9672 // loadgroup.
9673 EnsureOnloadBlocker();
9676 // Step 8 -- clear event listeners out of our DOM tree
9677 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9678 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9679 elm->RemoveAllListeners();
9683 // Step 9 -- clear event listeners from our window, if we have one.
9685 // Note that we explicitly want the inner window, and only if we're its
9686 // document. We want to do this (per spec) even when we're not the "active
9687 // document", so we can't go through GetWindow(), because it might forward to
9688 // the wrong inner.
9689 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9690 if (win->GetExtantDoc() == this) {
9691 if (EventListenerManager* elm =
9692 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9693 elm->RemoveAllListeners();
9698 // If we have a parser that has a zero script nesting level, we need to
9699 // properly terminate it. We do that after we've removed all the event
9700 // listeners (so termination won't trigger event listeners if it does
9701 // something to the DOM), but before we remove all elements from the document
9702 // (so if termination does modify the DOM in some way we will just blow it
9703 // away immediately. See the similar code in WriteCommon that handles the
9704 // !IsInsertionPointDefined() case and should stay in sync with this code.
9705 if (mParser) {
9706 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9707 "Why didn't we take the early return?");
9708 // Make sure we don't re-enter.
9709 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9710 mParser->Terminate();
9711 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9714 // Step 10 -- remove all our DOM kids without firing any mutation events.
9716 // We want to ignore any recursive calls to Open() that happen while
9717 // disconnecting the node tree. The spec doesn't say to do this, but the
9718 // spec also doesn't envision unload events on subframes firing while we do
9719 // this, while all browsers fire them in practice. See
9720 // <https://github.com/whatwg/html/issues/4611>.
9721 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9722 DisconnectNodeTree();
9725 // Step 11 -- if we're the current document in our docshell, do the
9726 // equivalent of pushState() with the new URL we should have.
9727 if (shell && IsCurrentActiveDocument()) {
9728 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9729 if (callerDoc != this) {
9730 nsCOMPtr<nsIURI> noFragmentURI;
9731 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9732 if (NS_WARN_IF(NS_FAILED(rv))) {
9733 aError.Throw(rv);
9734 return nullptr;
9736 newURI = std::move(noFragmentURI);
9739 // UpdateURLAndHistory might do various member-setting, so make sure we're
9740 // holding strong refs to all the refcounted args on the stack. We can
9741 // assume that our caller is holding on to "this" already.
9742 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9743 bool equalURIs;
9744 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9745 if (NS_WARN_IF(NS_FAILED(rv))) {
9746 aError.Throw(rv);
9747 return nullptr;
9749 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9750 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9751 /* aReplace = */ true, currentURI,
9752 equalURIs);
9753 if (NS_WARN_IF(NS_FAILED(rv))) {
9754 aError.Throw(rv);
9755 return nullptr;
9758 // And use the security info of the caller document as well, since
9759 // it's the thing providing our data.
9760 mSecurityInfo = callerDoc->GetSecurityInfo();
9762 // This is not mentioned in the spec, but I think that's a spec bug. See
9763 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9764 // URL may be changing away from about:blank here, we really want to unset
9765 // this flag no matter what, since only about:blank can be an initial
9766 // document.
9767 SetIsInitialDocument(false);
9769 // And let our docloader know that it will need to track our load event.
9770 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9773 // Per spec nothing happens with our URI in other cases, though note
9774 // <https://github.com/whatwg/html/issues/4286>.
9776 // Note that we don't need to do anything here with base URIs per spec.
9777 // That said, this might be assuming that we implement
9778 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9779 // correctly, which we don't right now for the about:blank case.
9781 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9782 mSkipLoadEventAfterClose = mLoadEventFiring;
9784 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9785 // we do anything else, so we can then proceed to later ready state levels.
9786 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9787 /* updateTimingInformation = */ false);
9788 // Reset a flag that affects readyState behavior.
9789 mSetCompleteAfterDOMContentLoaded = false;
9791 // Step 13 -- set our compat mode to standards.
9792 SetCompatibilityMode(eCompatibility_FullStandards);
9794 // Step 14 -- create a new parser associated with document. This also does
9795 // step 16 implicitly.
9796 mParserAborted = false;
9797 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9798 mParser = parser;
9799 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9800 nsresult rv = parser->StartExecutor();
9801 if (NS_WARN_IF(NS_FAILED(rv))) {
9802 aError.Throw(rv);
9803 return nullptr;
9806 // Clear out our form control state, because the state of controls
9807 // in the pre-open() document should not affect the state of
9808 // controls that are now going to be written.
9809 mLayoutHistoryState = nullptr;
9811 if (shell) {
9812 // Prepare the docshell and the document viewer for the impending
9813 // out-of-band document.write()
9814 shell->PrepareForNewContentModel();
9816 nsCOMPtr<nsIContentViewer> cv;
9817 shell->GetContentViewer(getter_AddRefs(cv));
9818 if (cv) {
9819 cv->LoadStart(this);
9823 // Step 15.
9824 SetReadyStateInternal(Document::READYSTATE_LOADING,
9825 /* updateTimingInformation = */ false);
9827 // Step 16 happened with step 14 above.
9829 // Step 17.
9830 return this;
9833 void Document::Close(ErrorResult& rv) {
9834 if (!IsHTMLDocument()) {
9835 // No calling document.close() on XHTML!
9837 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9838 return;
9841 if (ShouldThrowOnDynamicMarkupInsertion()) {
9842 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9843 return;
9846 if (!mParser || !mParser->IsScriptCreated()) {
9847 return;
9850 ++mWriteLevel;
9851 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9852 ->Parse(u""_ns, nullptr, true);
9853 --mWriteLevel;
9856 void Document::WriteCommon(const Sequence<nsString>& aText,
9857 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9858 // Fast path the common case
9859 if (aText.Length() == 1) {
9860 WriteCommon(aText[0], aNewlineTerminate, rv);
9861 } else {
9862 // XXXbz it would be nice if we could pass all the strings to the parser
9863 // without having to do all this copying and then ask it to start
9864 // parsing....
9865 nsString text;
9866 for (size_t i = 0; i < aText.Length(); ++i) {
9867 text.Append(aText[i]);
9869 WriteCommon(text, aNewlineTerminate, rv);
9873 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9874 ErrorResult& aRv) {
9875 #ifdef DEBUG
9877 // Assert that we do not use or accidentally introduce doc.write()
9878 // in system privileged context or in any of our about: pages.
9879 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9880 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9881 if (!isAboutOrPrivContext) {
9882 if (principal->SchemeIs("about")) {
9883 // about:blank inherits the security contetext and this assertion
9884 // is only meant for actual about: pages.
9885 nsAutoCString host;
9886 principal->GetHost(host);
9887 isAboutOrPrivContext = !host.EqualsLiteral("blank");
9890 // Some automated tests use an empty string to kick off some parsing
9891 // mechansims, but they do not do any harm since they use an empty string.
9892 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
9893 "do not use doc.write in privileged context!");
9895 #endif
9897 mTooDeepWriteRecursion =
9898 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9899 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9900 aRv.Throw(NS_ERROR_UNEXPECTED);
9901 return;
9904 if (!IsHTMLDocument() || mDisableDocWrite) {
9905 // No calling document.write*() on XHTML!
9907 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9908 return;
9911 if (ShouldThrowOnDynamicMarkupInsertion()) {
9912 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9913 return;
9916 if (mParserAborted) {
9917 // Hixie says aborting the parser doesn't undefine the insertion point.
9918 // However, since we null out mParser in that case, we track the
9919 // theoretically defined insertion point using mParserAborted.
9920 return;
9923 // Implement Step 4.1 of:
9924 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
9925 if (ShouldIgnoreOpens()) {
9926 return;
9929 void* key = GenerateParserKey();
9930 if (mParser && !mParser->IsInsertionPointDefined()) {
9931 if (mIgnoreDestructiveWritesCounter) {
9932 // Instead of implying a call to document.open(), ignore the call.
9933 nsContentUtils::ReportToConsole(
9934 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9935 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9936 return;
9938 // The spec doesn't tell us to ignore opens from here, but we need to
9939 // ensure opens are ignored here. See similar code in Open() that handles
9940 // the case of an existing parser which is not currently running script and
9941 // should stay in sync with this code.
9942 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9943 mParser->Terminate();
9944 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9947 if (!mParser) {
9948 if (mIgnoreDestructiveWritesCounter) {
9949 // Instead of implying a call to document.open(), ignore the call.
9950 nsContentUtils::ReportToConsole(
9951 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9952 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9953 return;
9956 Open({}, {}, aRv);
9958 // If Open() fails, or if it didn't create a parser (as it won't
9959 // if the user chose to not discard the current document through
9960 // onbeforeunload), don't write anything.
9961 if (aRv.Failed() || !mParser) {
9962 return;
9966 static constexpr auto new_line = u"\n"_ns;
9968 ++mWriteLevel;
9970 // This could be done with less code, but for performance reasons it
9971 // makes sense to have the code for two separate Parse() calls here
9972 // since the concatenation of strings costs more than we like. And
9973 // why pay that price when we don't need to?
9974 if (aNewlineTerminate) {
9975 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
9976 ->Parse(aText + new_line, key, false);
9977 } else {
9978 aRv =
9979 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
9982 --mWriteLevel;
9984 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
9987 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
9988 WriteCommon(aText, false, rv);
9991 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
9992 WriteCommon(aText, true, rv);
9995 void* Document::GenerateParserKey(void) {
9996 if (!mScriptLoader) {
9997 // If we don't have a script loader, then the parser probably isn't parsing
9998 // anything anyway, so just return null.
9999 return nullptr;
10002 // The script loader provides us with the currently executing script element,
10003 // which is guaranteed to be unique per script.
10004 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
10005 if (script && mParser && mParser->IsScriptCreated()) {
10006 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
10007 if (creatorParser != mParser) {
10008 // Make scripts that aren't inserted by the active parser of this document
10009 // participate in the context of the script that document.open()ed
10010 // this document.
10011 return nullptr;
10014 return script;
10017 /* static */
10018 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
10019 nsAtom* aAtom, void* aData) {
10020 MOZ_ASSERT(aElement, "Must have element to work with!");
10022 if (!aElement->HasName()) {
10023 return false;
10026 nsString* elementName = static_cast<nsString*>(aData);
10027 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
10028 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
10029 eCaseMatters);
10032 /* static */
10033 void* Document::UseExistingNameString(nsINode* aRootNode,
10034 const nsString* aName) {
10035 return const_cast<nsString*>(aName);
10038 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
10039 if (mDocumentURI) {
10040 nsAutoCString uri;
10041 nsresult rv = mDocumentURI->GetSpec(uri);
10042 NS_ENSURE_SUCCESS(rv, rv);
10044 CopyUTF8toUTF16(uri, aDocumentURI);
10045 } else {
10046 aDocumentURI.Truncate();
10049 return NS_OK;
10052 // Alias of above
10053 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
10055 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
10056 CallerType aCallerType,
10057 ErrorResult& aRv) const {
10058 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
10059 aRv = GetDocumentURI(aDocumentURI);
10060 return;
10063 nsAutoCString uri;
10064 nsresult res = mChromeXHRDocURI->GetSpec(uri);
10065 if (NS_FAILED(res)) {
10066 aRv.Throw(res);
10067 return;
10069 CopyUTF8toUTF16(uri, aDocumentURI);
10072 nsIURI* Document::GetDocumentURIObject() const {
10073 if (!mChromeXHRDocURI) {
10074 return GetDocumentURI();
10077 return mChromeXHRDocURI;
10080 void Document::GetCompatMode(nsString& aCompatMode) const {
10081 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
10082 mCompatMode == eCompatibility_AlmostStandards ||
10083 mCompatMode == eCompatibility_FullStandards,
10084 "mCompatMode is neither quirks nor strict for this document");
10086 if (mCompatMode == eCompatibility_NavQuirks) {
10087 aCompatMode.AssignLiteral("BackCompat");
10088 } else {
10089 aCompatMode.AssignLiteral("CSS1Compat");
10093 } // namespace dom
10094 } // namespace mozilla
10096 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
10097 if (Element* element = Element::FromNode(aNode)) {
10098 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
10099 while (true) {
10100 RefPtr<Attr> attr;
10102 // Use an iterator to get an arbitrary attribute from the
10103 // cache. The iterator must be destroyed before any other
10104 // operations on mAttributeCache, to avoid hash table
10105 // assertions.
10106 auto iter = map->mAttributeCache.ConstIter();
10107 if (iter.Done()) {
10108 break;
10110 attr = iter.UserData();
10113 BlastSubtreeToPieces(attr);
10115 mozilla::DebugOnly<nsresult> rv =
10116 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10117 attr->NodeInfo()->NameAtom(), false);
10119 // XXX Should we abort here?
10120 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10124 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10125 BlastSubtreeToPieces(shadow);
10126 element->UnattachShadow();
10130 while (aNode->HasChildren()) {
10131 nsIContent* node = aNode->GetFirstChild();
10132 BlastSubtreeToPieces(node);
10133 aNode->RemoveChildNode(node, false);
10137 namespace mozilla::dom {
10139 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) {
10140 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10142 // Scope firing mutation events so that we don't carry any state that
10143 // might be stale
10145 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10146 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10150 nsAutoScriptBlocker scriptBlocker;
10152 switch (adoptedNode->NodeType()) {
10153 case ATTRIBUTE_NODE: {
10154 // Remove from ownerElement.
10155 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10157 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement(rv);
10158 if (rv.Failed()) {
10159 return nullptr;
10162 if (ownerElement) {
10163 OwningNonNull<Attr> newAttr =
10164 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10165 if (rv.Failed()) {
10166 return nullptr;
10170 break;
10172 case DOCUMENT_FRAGMENT_NODE: {
10173 if (adoptedNode->IsShadowRoot()) {
10174 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10175 return nullptr;
10177 [[fallthrough]];
10179 case ELEMENT_NODE:
10180 case PROCESSING_INSTRUCTION_NODE:
10181 case TEXT_NODE:
10182 case CDATA_SECTION_NODE:
10183 case COMMENT_NODE:
10184 case DOCUMENT_TYPE_NODE: {
10185 // Don't allow adopting a node's anonymous subtree out from under it.
10186 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10187 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10188 return nullptr;
10191 // We don't want to adopt an element into its own contentDocument or into
10192 // a descendant contentDocument, so we check if the frameElement of this
10193 // document or any of its parents is the adopted node or one of its
10194 // descendants.
10195 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10196 while (bc) {
10197 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10198 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10199 rv.ThrowHierarchyRequestError(
10200 "Trying to adopt a node into its own contentDocument or a "
10201 "descendant contentDocument.");
10202 return nullptr;
10205 if (XRE_IsParentProcess()) {
10206 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10207 } else {
10208 bc = bc->GetParent();
10212 // Remove from parent.
10213 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10214 if (parent) {
10215 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10216 } else {
10217 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10220 break;
10222 case DOCUMENT_NODE: {
10223 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10224 return nullptr;
10226 default: {
10227 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10229 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10230 return nullptr;
10234 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10235 bool sameDocument = oldDocument == this;
10237 AutoJSContext cx;
10238 JS::Rooted<JSObject*> newScope(cx, nullptr);
10239 if (!sameDocument) {
10240 newScope = GetWrapper();
10241 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10242 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10243 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10244 // false, and documents should always insist on being wrapped in an
10245 // canonical scope. But we try to pass something sane anyway.
10246 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10247 JSAutoRealm ar(cx, globalObject);
10248 JS::Rooted<JS::Value> v(cx);
10249 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10250 /* aAllowWrapping = */ false);
10251 if (rv.Failed()) return nullptr;
10252 newScope = &v.toObject();
10256 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10257 if (rv.Failed()) {
10258 // Disconnect all nodes from their parents, since some have the old document
10259 // as their ownerDocument and some have this as their ownerDocument.
10260 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10261 return nullptr;
10264 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10265 "Should still be in the document we just got adopted into");
10267 return adoptedNode;
10270 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10272 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10273 const nsString& aScaleString) {
10274 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10275 if (aScaleString.EqualsLiteral("device-width") ||
10276 aScaleString.EqualsLiteral("device-height")) {
10277 return Some(LayoutDeviceToScreenScale(10.0f));
10278 } else if (aScaleString.EqualsLiteral("yes")) {
10279 return Some(LayoutDeviceToScreenScale(1.0f));
10280 } else if (aScaleString.EqualsLiteral("no")) {
10281 return Some(LayoutDeviceToScreenScale(kViewportMinScale));
10282 } else if (aScaleString.IsEmpty()) {
10283 return Nothing();
10286 nsresult scaleErrorCode;
10287 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10288 if (NS_FAILED(scaleErrorCode)) {
10289 return Some(LayoutDeviceToScreenScale(kViewportMinScale));
10292 if (scale < 0) {
10293 return Nothing();
10295 return Some(clamped(LayoutDeviceToScreenScale(scale), kViewportMinScale,
10296 kViewportMaxScale));
10299 void Document::ParseScalesInViewportMetaData(
10300 const ViewportMetaData& aViewportMetaData) {
10301 Maybe<LayoutDeviceToScreenScale> scale;
10303 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10304 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10305 mValidScaleFloat = scale.isSome();
10307 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10308 // Chrome uses '5' for the fallback value of maximum-scale, we might
10309 // consider matching it in future.
10310 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10311 mScaleMaxFloat = scale.valueOr(kViewportMaxScale);
10312 mValidMaxScale = scale.isSome();
10314 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10315 mScaleMinFloat = scale.valueOr(kViewportMinScale);
10316 mValidMinScale = scale.isSome();
10318 // Resolve min-zoom and max-zoom values.
10319 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10320 if (mValidMaxScale && mValidMinScale) {
10321 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10325 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10326 const nsAString& aHeightString,
10327 bool aHasValidScale) {
10328 // The width and height properties
10329 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10331 // The width and height viewport <META> properties are translated into width
10332 // and height descriptors, setting the min-width/min-height value to
10333 // extend-to-zoom and the max-width/max-height value to the length from the
10334 // viewport <META> property as follows:
10336 // 1. Non-negative number values are translated to pixel lengths, clamped to
10337 // the range: [1px, 10000px]
10338 // 2. Negative number values are dropped
10339 // 3. device-width and device-height translate to 100vw and 100vh respectively
10340 // 4. Other keywords and unknown values are also dropped
10341 mMinWidth = nsViewportInfo::Auto;
10342 mMaxWidth = nsViewportInfo::Auto;
10343 if (!aWidthString.IsEmpty()) {
10344 mMinWidth = nsViewportInfo::ExtendToZoom;
10345 if (aWidthString.EqualsLiteral("device-width")) {
10346 mMaxWidth = nsViewportInfo::DeviceSize;
10347 } else {
10348 nsresult widthErrorCode;
10349 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10350 if (NS_FAILED(widthErrorCode)) {
10351 mMaxWidth = nsViewportInfo::Auto;
10352 } else if (mMaxWidth >= 0.0f) {
10353 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10354 } else {
10355 mMaxWidth = nsViewportInfo::Auto;
10358 } else if (aHasValidScale) {
10359 if (aHeightString.IsEmpty()) {
10360 mMinWidth = nsViewportInfo::ExtendToZoom;
10361 mMaxWidth = nsViewportInfo::ExtendToZoom;
10363 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10364 mMinWidth = nsViewportInfo::ExtendToZoom;
10365 mMaxWidth = nsViewportInfo::DeviceSize;
10368 mMinHeight = nsViewportInfo::Auto;
10369 mMaxHeight = nsViewportInfo::Auto;
10370 if (!aHeightString.IsEmpty()) {
10371 mMinHeight = nsViewportInfo::ExtendToZoom;
10372 if (aHeightString.EqualsLiteral("device-height")) {
10373 mMaxHeight = nsViewportInfo::DeviceSize;
10374 } else {
10375 nsresult heightErrorCode;
10376 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10377 if (NS_FAILED(heightErrorCode)) {
10378 mMaxHeight = nsViewportInfo::Auto;
10379 } else if (mMaxHeight >= 0.0f) {
10380 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10381 } else {
10382 mMaxHeight = nsViewportInfo::Auto;
10388 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10389 MOZ_ASSERT(mPresShell);
10391 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10392 // widget scale and the full zoom.
10393 nsPresContext* context = mPresShell->GetPresContext();
10394 // When querying the full zoom, get it from the device context rather than
10395 // directly from the pres context, because the device context's value can
10396 // include an adjustment necessay to keep the number of app units per device
10397 // pixel an integer, and we want the adjusted value.
10398 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10399 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10400 CSSToLayoutDeviceScale layoutDeviceScale =
10401 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10403 CSSToScreenScale defaultScale =
10404 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10406 // Special behaviour for desktop mode, provided we are not on an about: page,
10407 // or fullscreen.
10408 const bool fullscreen = Fullscreen();
10409 nsPIDOMWindowOuter* win = GetWindow();
10410 if (win && win->IsDesktopModeViewport() && !IsAboutPage() && !fullscreen) {
10411 CSSCoord viewportWidth =
10412 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10413 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10414 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10415 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10416 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10417 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10418 nsViewportInfo::ZoomFlag::AllowZoom,
10419 nsViewportInfo::ZoomBehaviour::Mobile);
10422 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10423 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10424 return nsViewportInfo(aDisplaySize, defaultScale,
10425 nsLayoutUtils::AllowZoomingForDocument(this)
10426 ? nsViewportInfo::ZoomFlag::AllowZoom
10427 : nsViewportInfo::ZoomFlag::DisallowZoom,
10428 StaticPrefs::apz_allow_zooming_out()
10429 ? nsViewportInfo::ZoomBehaviour::Mobile
10430 : nsViewportInfo::ZoomBehaviour::Desktop);
10433 // In cases where the width of the CSS viewport is less than or equal to the
10434 // width of the display (i.e. width <= device-width) then we disable
10435 // double-tap-to-zoom behaviour. See bug 941995 for details.
10437 switch (mViewportType) {
10438 case DisplayWidthHeight:
10439 return nsViewportInfo(aDisplaySize, defaultScale,
10440 nsViewportInfo::ZoomFlag::AllowZoom,
10441 nsViewportInfo::ZoomBehaviour::Mobile);
10442 case Unknown: {
10443 // We might early exit if the viewport is empty. Even if we don't,
10444 // at the end of this case we'll note that it was empty. Later, when
10445 // we're using the cached values, this will trigger alternate code paths.
10446 if (!mLastModifiedViewportMetaData) {
10447 // If the docType specifies that we are on a site optimized for mobile,
10448 // then we want to return specially crafted defaults for the viewport
10449 // info.
10450 if (RefPtr<DocumentType> docType = GetDoctype()) {
10451 nsAutoString docId;
10452 docType->GetPublicId(docId);
10453 if ((docId.Find("WAP") != -1) || (docId.Find("Mobile") != -1) ||
10454 (docId.Find("WML") != -1)) {
10455 // We're making an assumption that the docType can't change here
10456 mViewportType = DisplayWidthHeight;
10457 return nsViewportInfo(aDisplaySize, defaultScale,
10458 nsViewportInfo::ZoomFlag::AllowZoom,
10459 nsViewportInfo::ZoomBehaviour::Mobile);
10463 nsAutoString handheldFriendly;
10464 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10465 if (handheldFriendly.EqualsLiteral("true")) {
10466 mViewportType = DisplayWidthHeight;
10467 return nsViewportInfo(aDisplaySize, defaultScale,
10468 nsViewportInfo::ZoomFlag::AllowZoom,
10469 nsViewportInfo::ZoomBehaviour::Mobile);
10473 ViewportMetaData metaData = GetViewportMetaData();
10475 // Parse initial-scale, minimum-scale and maximum-scale.
10476 ParseScalesInViewportMetaData(metaData);
10478 // Parse width and height properties
10479 // This function sets m{Min,Max}{Width,Height}.
10480 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10481 mValidScaleFloat);
10483 mAllowZoom = true;
10484 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10485 (metaData.mUserScalable.EqualsLiteral("no")) ||
10486 (metaData.mUserScalable.EqualsLiteral("false"))) {
10487 mAllowZoom = false;
10490 // Resolve viewport-fit value.
10491 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10492 mViewportFit = ViewportFitType::Auto;
10493 if (!metaData.mViewportFit.IsEmpty()) {
10494 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10495 mViewportFit = ViewportFitType::Contain;
10496 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10497 mViewportFit = ViewportFitType::Cover;
10501 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10503 mViewportType = Specified;
10504 [[fallthrough]];
10506 case Specified:
10507 default:
10508 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10509 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10510 bool effectiveValidMaxScale = mValidMaxScale;
10512 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10513 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10514 : nsViewportInfo::ZoomFlag::DisallowZoom;
10515 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10516 // If the pref to force user-scalable is enabled, we ignore the values
10517 // from the meta-viewport tag for these properties and just assume they
10518 // allow the page to be scalable. Note in particular that this code is
10519 // in the "Specified" branch of the enclosing switch statement, so that
10520 // calls to GetViewportInfo always use the latest value of the
10521 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10522 // return nsViewportInfo instances are all consistent with
10523 // browser_ui_zoom_force_user_scalable() already.
10524 effectiveMinScale = kViewportMinScale;
10525 effectiveMaxScale = kViewportMaxScale;
10526 effectiveValidMaxScale = true;
10527 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10530 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10531 auto ComputeExtendZoom = [&]() -> float {
10532 if (mValidScaleFloat && effectiveValidMaxScale) {
10533 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10535 if (mValidScaleFloat) {
10536 return mScaleFloat.scale;
10538 if (effectiveValidMaxScale) {
10539 return effectiveMaxScale.scale;
10541 return nsViewportInfo::Auto;
10544 // Resolving 'extend-to-zoom'
10545 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10546 float extendZoom = ComputeExtendZoom();
10548 CSSCoord minWidth = mMinWidth;
10549 CSSCoord maxWidth = mMaxWidth;
10550 CSSCoord minHeight = mMinHeight;
10551 CSSCoord maxHeight = mMaxHeight;
10553 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10554 // viewport size. We need to use this scaled size for any clamping of
10555 // width or height.
10556 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10558 // Our min and max width and height values are mostly as specified by
10559 // the viewport declaration, but we make an exception for max width.
10560 // Max width, if auto, and if there's no initial-scale, will be set
10561 // to a default size. This is to support legacy site design with no
10562 // viewport declaration, and to do that using the same scheme as
10563 // Chrome does, in order to maintain web compatibility. Since the
10564 // default size has a complicated calculation, we fixup the maxWidth
10565 // value after setting it, above.
10566 if (maxWidth == nsViewportInfo::Auto && !mValidScaleFloat) {
10567 BrowsingContext* bc = GetBrowsingContext();
10568 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10569 bc->InRDMPane()) {
10570 // If RDM and touch simulation are active, then use the simulated
10571 // screen width to accomodate for cases where the screen width is
10572 // larger than the desktop viewport default.
10573 maxWidth = nsViewportInfo::Max(
10574 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10575 } else {
10576 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10578 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10579 // to keep device pixel size unchanged after full zoom applied.
10580 // See bug 974242.
10581 maxWidth /= fullZoom;
10583 // We set minWidth to ExtendToZoom, which will cause our later width
10584 // calculation to expand to maxWidth, if scale restrictions allow it.
10585 minWidth = nsViewportInfo::ExtendToZoom;
10588 // Resolve device-width and device-height first.
10589 if (maxWidth == nsViewportInfo::DeviceSize) {
10590 maxWidth = displaySize.width;
10592 if (maxHeight == nsViewportInfo::DeviceSize) {
10593 maxHeight = displaySize.height;
10595 if (extendZoom == nsViewportInfo::Auto) {
10596 if (maxWidth == nsViewportInfo::ExtendToZoom) {
10597 maxWidth = nsViewportInfo::Auto;
10599 if (maxHeight == nsViewportInfo::ExtendToZoom) {
10600 maxHeight = nsViewportInfo::Auto;
10602 if (minWidth == nsViewportInfo::ExtendToZoom) {
10603 minWidth = maxWidth;
10605 if (minHeight == nsViewportInfo::ExtendToZoom) {
10606 minHeight = maxHeight;
10608 } else {
10609 CSSSize extendSize = displaySize / extendZoom;
10610 if (maxWidth == nsViewportInfo::ExtendToZoom) {
10611 maxWidth = extendSize.width;
10613 if (maxHeight == nsViewportInfo::ExtendToZoom) {
10614 maxHeight = extendSize.height;
10616 if (minWidth == nsViewportInfo::ExtendToZoom) {
10617 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10619 if (minHeight == nsViewportInfo::ExtendToZoom) {
10620 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10624 // Resolve initial width and height from min/max descriptors
10625 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10626 CSSCoord width = nsViewportInfo::Auto;
10627 if (minWidth != nsViewportInfo::Auto ||
10628 maxWidth != nsViewportInfo::Auto) {
10629 width = nsViewportInfo::Max(
10630 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10632 CSSCoord height = nsViewportInfo::Auto;
10633 if (minHeight != nsViewportInfo::Auto ||
10634 maxHeight != nsViewportInfo::Auto) {
10635 height = nsViewportInfo::Max(
10636 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10639 // Resolve width value
10640 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10641 if (width == nsViewportInfo::Auto) {
10642 if (height == nsViewportInfo::Auto || aDisplaySize.height == 0) {
10643 width = displaySize.width;
10644 } else {
10645 width = height * aDisplaySize.width / aDisplaySize.height;
10649 // Resolve height value
10650 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10651 if (height == nsViewportInfo::Auto) {
10652 if (aDisplaySize.width == 0) {
10653 height = displaySize.height;
10654 } else {
10655 height = width * aDisplaySize.height / aDisplaySize.width;
10658 MOZ_ASSERT(width != nsViewportInfo::Auto &&
10659 height != nsViewportInfo::Auto);
10661 CSSSize size(width, height);
10663 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10664 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10665 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10667 nsViewportInfo::AutoSizeFlag sizeFlag =
10668 nsViewportInfo::AutoSizeFlag::FixedSize;
10669 if (mMaxWidth == nsViewportInfo::DeviceSize ||
10670 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::DeviceSize ||
10671 mScaleFloat.scale == 1.0f)) ||
10672 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::Auto &&
10673 mMaxHeight < 0)) {
10674 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10677 // FIXME: Resolving width and height should be done above 'Resolve width
10678 // value' and 'Resolve height value'.
10679 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10680 size = displaySize;
10683 // The purpose of clamping the viewport width to a minimum size is to
10684 // prevent page authors from setting it to a ridiculously small value.
10685 // If the page is actually being rendered in a very small area (as might
10686 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10687 // prevent the viewport from taking on that size.
10688 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10690 size.width = clamped(size.width, effectiveMinSize.width,
10691 float(kViewportMaxSize.width));
10693 // Also recalculate the default zoom, if it wasn't specified in the
10694 // metadata, and the width is specified.
10695 if (!mValidScaleFloat && !mWidthStrEmpty) {
10696 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10697 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10700 size.height = clamped(size.height, effectiveMinSize.height,
10701 float(kViewportMaxSize.height));
10703 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10704 // min and max, and then use the clamped value for the scale, the min, and
10705 // the max. If we don't have a positive scale, assert that we are setting
10706 // the auto scale flag.
10707 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10708 scaleFloat > CSSToScreenScale(0.0f)) {
10709 scaleFloat = scaleMinFloat = scaleMaxFloat =
10710 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10712 MOZ_ASSERT(
10713 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10714 "If we don't have a positive scale, we should be using auto scale.");
10716 // We need to perform a conversion, but only if the initial or maximum
10717 // scale were set explicitly by the user.
10718 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10719 scaleFloat <= scaleMaxFloat) {
10720 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10721 size.width = std::max(size.width, displaySize.width);
10722 size.height = std::max(size.height, displaySize.height);
10723 } else if (effectiveValidMaxScale) {
10724 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10725 size.width = std::max(size.width, displaySize.width);
10726 size.height = std::max(size.height, displaySize.height);
10729 return nsViewportInfo(
10730 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10731 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10732 : nsViewportInfo::AutoScaleFlag::AutoScale,
10733 effectiveZoomFlag, mViewportFit);
10737 ViewportMetaData Document::GetViewportMetaData() const {
10738 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
10739 : ViewportMetaData();
10742 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
10743 mLastModifiedViewportMetaData = std::move(aData);
10744 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10745 mViewportType = Unknown;
10747 RefPtr<AsyncEventDispatcher> asyncDispatcher =
10748 new AsyncEventDispatcher(this, u"DOMMetaViewportFitChanged"_ns,
10749 CanBubble::eYes, ChromeOnlyDispatch::eYes);
10750 asyncDispatcher->RunDOMEventWhenSafe();
10753 EventListenerManager* Document::GetOrCreateListenerManager() {
10754 if (!mListenerManager) {
10755 mListenerManager =
10756 new EventListenerManager(static_cast<EventTarget*>(this));
10757 SetFlags(NODE_HAS_LISTENERMANAGER);
10760 return mListenerManager;
10763 EventListenerManager* Document::GetExistingListenerManager() const {
10764 return mListenerManager;
10767 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10768 aVisitor.mCanHandle = true;
10769 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10770 // Bug 329119
10771 aVisitor.mForceContentDispatch = true;
10773 // Load events must not propagate to |window| object, see bug 335251.
10774 if (aVisitor.mEvent->mMessage != eLoad) {
10775 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10776 aVisitor.SetParentTarget(
10777 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10781 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10782 CallerType aCallerType,
10783 ErrorResult& rv) const {
10784 nsPresContext* presContext = GetPresContext();
10786 // Create event even without presContext.
10787 RefPtr<Event> ev =
10788 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10789 nullptr, aEventType, aCallerType);
10790 if (!ev) {
10791 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10792 return nullptr;
10794 WidgetEvent* e = ev->WidgetEventPtr();
10795 e->mFlags.mBubbles = false;
10796 e->mFlags.mCancelable = false;
10797 return ev.forget();
10800 void Document::FlushPendingNotifications(FlushType aType) {
10801 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10802 FlushPendingNotifications(flush);
10805 class nsDocumentOnStack {
10806 public:
10807 explicit nsDocumentOnStack(Document* aDoc) : mDoc(aDoc) {
10808 mDoc->IncreaseStackRefCnt();
10810 ~nsDocumentOnStack() { mDoc->DecreaseStackRefCnt(); }
10812 private:
10813 Document* mDoc;
10816 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10817 FlushType flushType = aFlush.mFlushType;
10819 nsDocumentOnStack dos(this);
10821 // We need to flush the sink for non-HTML documents (because the XML
10822 // parser still does insertion with deferred notifications). We
10823 // also need to flush the sink if this is a layout-related flush, to
10824 // make sure that layout is started as needed. But we can skip that
10825 // part if we have no presshell or if it's already done an initial
10826 // reflow.
10827 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10828 mPresShell && !mPresShell->DidInitialize())) &&
10829 (mParser || mWeakSink)) {
10830 nsCOMPtr<nsIContentSink> sink;
10831 if (mParser) {
10832 sink = mParser->GetContentSink();
10833 } else {
10834 sink = do_QueryReferent(mWeakSink);
10835 if (!sink) {
10836 mWeakSink = nullptr;
10839 // Determine if it is safe to flush the sink notifications
10840 // by determining if it safe to flush all the presshells.
10841 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10842 sink->FlushPendingNotifications(flushType);
10846 // Should we be flushing pending binding constructors in here?
10848 if (flushType <= FlushType::ContentAndNotify) {
10849 // Nothing to do here
10850 return;
10853 // If we have a parent we must flush the parent too to ensure that our
10854 // container is reflowed if its size was changed.
10856 // We do it only if the subdocument and the parent can observe each other
10857 // synchronously (that is, if we're not cross-origin), to avoid work that is
10858 // not observable, and if the parent document has finished loading all its
10859 // render-blocking stylesheets and may start laying out the document, to avoid
10860 // unnecessary flashes of unstyled content on the parent document. Note that
10861 // this last bit means that size-dependent media queries in this document may
10862 // produce incorrect results temporarily.
10864 // But if it's not safe to flush ourselves, then don't flush the parent, since
10865 // that can cause things like resizes of our frame's widget, which we can't
10866 // handle while flushing is unsafe.
10867 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10868 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10869 ChangesToFlush parentFlush = aFlush;
10870 if (flushType >= FlushType::Style) {
10871 // Since media queries mean that a size change of our container can affect
10872 // style, we need to promote a style flush on ourself to a layout flush on
10873 // our parent, since we need our container to be the correct size to
10874 // determine the correct style.
10875 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10877 mParentDocument->FlushPendingNotifications(parentFlush);
10880 if (RefPtr<PresShell> presShell = GetPresShell()) {
10881 presShell->FlushPendingNotifications(aFlush);
10885 void Document::FlushExternalResources(FlushType aType) {
10886 NS_ASSERTION(
10887 aType >= FlushType::Style,
10888 "should only need to flush for style or higher in external resources");
10889 if (GetDisplayDocument()) {
10890 return;
10893 auto flush = [aType](Document& aDoc) {
10894 aDoc.FlushPendingNotifications(aType);
10895 return CallState::Continue;
10898 EnumerateExternalResources(flush);
10901 void Document::SetXMLDeclaration(const char16_t* aVersion,
10902 const char16_t* aEncoding,
10903 const int32_t aStandalone) {
10904 if (!aVersion || *aVersion == '\0') {
10905 mXMLDeclarationBits = 0;
10906 return;
10909 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10911 if (aEncoding && *aEncoding != '\0') {
10912 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10915 if (aStandalone == 1) {
10916 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10917 XML_DECLARATION_BITS_STANDALONE_YES;
10918 } else if (aStandalone == 0) {
10919 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10923 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10924 nsAString& aStandalone) {
10925 aVersion.Truncate();
10926 aEncoding.Truncate();
10927 aStandalone.Truncate();
10929 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10930 return;
10933 // always until we start supporting 1.1 etc.
10934 aVersion.AssignLiteral("1.0");
10936 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
10937 // This is what we have stored, not necessarily what was written
10938 // in the original
10939 GetCharacterSet(aEncoding);
10942 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
10943 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
10944 aStandalone.AssignLiteral("yes");
10945 } else {
10946 aStandalone.AssignLiteral("no");
10951 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
10952 mColorSchemeMetaTags.Insert(aMeta);
10953 RecomputeColorScheme();
10956 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
10957 mColorSchemeMetaTags.RemoveElement(aMeta);
10958 RecomputeColorScheme();
10961 void Document::RecomputeColorScheme() {
10962 if (!StaticPrefs::layout_css_color_scheme_enabled()) {
10963 return;
10965 auto oldColorScheme = mColorSchemeBits;
10966 mColorSchemeBits = 0;
10967 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
10968 for (const HTMLMetaElement* el : elements) {
10969 nsAutoString content;
10970 if (!el->GetAttr(nsGkAtoms::content, content)) {
10971 continue;
10974 NS_ConvertUTF16toUTF8 contentU8(content);
10975 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
10976 break;
10980 if (mColorSchemeBits == oldColorScheme) {
10981 return;
10984 if (nsPresContext* pc = GetPresContext()) {
10985 // This affects system colors, which are inherited, so we need to recascade.
10986 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
10990 bool Document::IsScriptEnabled() {
10991 // If this document is sandboxed without 'allow-scripts'
10992 // script is not enabled
10993 if (HasScriptsBlockedBySandbox()) {
10994 return false;
10997 nsCOMPtr<nsIScriptGlobalObject> globalObject =
10998 do_QueryInterface(GetInnerWindow());
10999 if (!globalObject || !globalObject->HasJSGlobal()) {
11000 return false;
11003 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
11004 .Allowed();
11007 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
11008 PRTime modDate = 0;
11009 nsresult rv;
11011 nsCOMPtr<nsIHttpChannel> httpChannel;
11012 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
11013 if (NS_WARN_IF(NS_FAILED(rv))) {
11014 return;
11017 if (httpChannel) {
11018 nsAutoCString tmp;
11019 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
11021 if (NS_SUCCEEDED(rv)) {
11022 PRTime time;
11023 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
11024 if (st == PR_SUCCESS) {
11025 modDate = time;
11029 static const char* const headers[] = {
11030 "default-style", "content-style-type", "content-language",
11031 "content-disposition", "refresh", "x-dns-prefetch-control",
11032 "x-frame-options", "origin-trial",
11033 // add more http headers if you need
11034 // XXXbz don't add content-location support without reading bug
11035 // 238654 and its dependencies/dups first.
11038 nsAutoCString headerVal;
11039 const char* const* name = headers;
11040 while (*name) {
11041 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
11042 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
11043 RefPtr<nsAtom> key = NS_Atomize(*name);
11044 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
11046 ++name;
11048 } else {
11049 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
11050 if (fileChannel) {
11051 nsCOMPtr<nsIFile> file;
11052 fileChannel->GetFile(getter_AddRefs(file));
11053 if (file) {
11054 PRTime msecs;
11055 rv = file->GetLastModifiedTime(&msecs);
11057 if (NS_SUCCEEDED(rv)) {
11058 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
11061 } else {
11062 nsAutoCString contentDisp;
11063 rv = aChannel->GetContentDispositionHeader(contentDisp);
11064 if (NS_SUCCEEDED(rv)) {
11065 SetHeaderData(nsGkAtoms::headerContentDisposition,
11066 NS_ConvertASCIItoUTF16(contentDisp));
11071 mLastModified.Truncate();
11072 if (modDate != 0) {
11073 GetFormattedTimeString(modDate, mLastModified);
11077 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
11078 // set any HTTP-EQUIV data into document's header data as well as url
11079 nsAutoString header;
11080 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
11081 if (!header.IsEmpty()) {
11082 // Ignore META REFRESH when document is sandboxed from automatic features.
11083 nsContentUtils::ASCIIToLower(header);
11084 if (nsGkAtoms::refresh->Equals(header) &&
11085 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
11086 return;
11089 nsAutoString result;
11090 aMetaElement->GetAttr(nsGkAtoms::content, result);
11091 if (!result.IsEmpty()) {
11092 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
11093 SetHeaderData(fieldAtom, result);
11097 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
11098 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
11099 nsAutoString result;
11100 aMetaElement->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result);
11101 if (!result.IsEmpty()) {
11102 nsContentUtils::ASCIIToLower(result);
11103 SetHeaderData(nsGkAtoms::handheldFriendly, result);
11108 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
11109 nsAtom* aPrefix,
11110 int32_t aNamespaceID,
11111 const nsAString* aIs) {
11112 #ifdef DEBUG
11113 nsAutoString qName;
11114 if (aPrefix) {
11115 aPrefix->ToString(qName);
11116 qName.Append(':');
11118 qName.Append(aName);
11120 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11121 // Document::CreateElement can call us with such a name and no prefix,
11122 // which would cause an error if we just used true here.
11123 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11124 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11125 "Don't pass invalid prefixes to Document::CreateElem, "
11126 "check caller.");
11127 #endif
11129 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11130 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11131 getter_AddRefs(nodeInfo));
11132 NS_ENSURE_TRUE(nodeInfo, nullptr);
11134 nsCOMPtr<Element> element;
11135 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11136 NOT_FROM_PARSER, aIs);
11137 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11140 bool Document::IsSafeToFlush() const {
11141 PresShell* presShell = GetPresShell();
11142 if (!presShell) {
11143 return true;
11145 return presShell->IsSafeToFlush();
11148 void Document::Sanitize() {
11149 // Sanitize the document by resetting all (current and former) password fields
11150 // and any form fields with autocomplete=off to their default values. We do
11151 // this now, instead of when the presentation is restored, to offer some
11152 // protection in case there is ever an exploit that allows a cached document
11153 // to be accessed from a different document.
11155 // First locate all input elements, regardless of whether they are
11156 // in a form, and reset the password and autocomplete=off elements.
11158 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11160 nsAutoString value;
11162 uint32_t length = nodes->Length(true);
11163 for (uint32_t i = 0; i < length; ++i) {
11164 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11166 RefPtr<HTMLInputElement> input =
11167 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11168 if (!input) continue;
11170 input->GetAttr(nsGkAtoms::autocomplete, value);
11171 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11172 input->Reset();
11176 // Now locate all _form_ elements that have autocomplete=off and reset them
11177 nodes = GetElementsByTagName(u"form"_ns);
11179 length = nodes->Length(true);
11180 for (uint32_t i = 0; i < length; ++i) {
11181 // Reset() may change the list dynamically.
11182 RefPtr<HTMLFormElement> form =
11183 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11184 if (!form) continue;
11186 form->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, value);
11187 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11191 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11192 if (!mSubDocuments) {
11193 return;
11196 // PLDHashTable::Iterator can't handle modifications while iterating so we
11197 // copy all entries to an array first before calling any callbacks.
11198 AutoTArray<RefPtr<Document>, 8> subdocs;
11199 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11200 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11201 if (Document* subdoc = entry->mSubDocument) {
11202 subdocs.AppendElement(subdoc);
11205 for (auto& subdoc : subdocs) {
11206 if (aCallback(*subdoc) == CallState::Stop) {
11207 break;
11212 void Document::CollectDescendantDocuments(
11213 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11214 if (!mSubDocuments) {
11215 return;
11218 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11219 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11220 const Document* subdoc = entry->mSubDocument;
11221 if (subdoc) {
11222 if (aCallback(subdoc)) {
11223 aDescendants.AppendElement(entry->mSubDocument);
11225 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11230 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11231 uint32_t& aBFCacheCombo,
11232 bool aIncludeSubdocuments,
11233 bool aAllowUnloadListeners) {
11234 bool ret = true;
11236 if (!IsBFCachingAllowed()) {
11237 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11238 ret = false;
11241 nsAutoCString uri;
11242 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11243 if (mDocumentURI) {
11244 mDocumentURI->GetSpec(uri);
11248 if (EventHandlingSuppressed()) {
11249 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11250 ("Save of %s blocked on event handling suppression", uri.get()));
11251 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11252 ret = false;
11255 // Do not allow suspended windows to be placed in the
11256 // bfcache. This method is also used to verify a document
11257 // coming out of the bfcache is ok to restore, though. So
11258 // we only want to block suspend windows that aren't also
11259 // frozen.
11260 nsPIDOMWindowInner* win = GetInnerWindow();
11261 if (win && win->IsSuspended() && !win->IsFrozen()) {
11262 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11263 ("Save of %s blocked on suspended Window", uri.get()));
11264 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11265 ret = false;
11268 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11269 bool thirdParty = false;
11270 // Currently some other mobile browsers seem to bfcache only cross-domain
11271 // pages, but bfcache those also when there are unload event listeners, so
11272 // this is trying to match that behavior as much as possible.
11273 bool allowUnloadListeners =
11274 aAllowUnloadListeners &&
11275 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11276 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11277 channel, &thirdParty)) &&
11278 thirdParty));
11280 // Check our event listener manager for unload/beforeunload listeners.
11281 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11282 if (!allowUnloadListeners && piTarget) {
11283 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11284 if (manager) {
11285 if (manager->HasUnloadListeners()) {
11286 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11287 ("Save of %s blocked due to unload handlers", uri.get()));
11288 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11289 ret = false;
11291 if (manager->HasBeforeUnloadListeners()) {
11292 if (!mozilla::SessionHistoryInParent() ||
11293 !StaticPrefs::
11294 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11295 MOZ_LOG(
11296 gPageCacheLog, mozilla::LogLevel::Verbose,
11297 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11298 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11299 ret = false;
11305 // Check if we have pending network requests
11306 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11307 if (loadGroup) {
11308 nsCOMPtr<nsISimpleEnumerator> requests;
11309 loadGroup->GetRequests(getter_AddRefs(requests));
11311 bool hasMore = false;
11313 // We want to bail out if we have any requests other than aNewRequest (or
11314 // in the case when aNewRequest is a part of a multipart response the base
11315 // channel the multipart response is coming in on).
11316 nsCOMPtr<nsIChannel> baseChannel;
11317 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11318 if (part) {
11319 part->GetBaseChannel(getter_AddRefs(baseChannel));
11322 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11323 nsCOMPtr<nsISupports> elem;
11324 requests->GetNext(getter_AddRefs(elem));
11326 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11327 if (request && request != aNewRequest && request != baseChannel) {
11328 // Favicon loads don't need to block caching.
11329 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11330 if (channel) {
11331 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11332 if (li->InternalContentPolicyType() ==
11333 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11334 continue;
11338 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11339 nsAutoCString requestName;
11340 request->GetName(requestName);
11341 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11342 ("Save of %s blocked because document has request %s",
11343 uri.get(), requestName.get()));
11345 aBFCacheCombo |= BFCacheStatus::REQUEST;
11346 ret = false;
11351 // Check if we have active GetUserMedia use
11352 if (MediaManager::Exists() && win &&
11353 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11354 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11355 ("Save of %s blocked due to GetUserMedia", uri.get()));
11356 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11357 ret = false;
11360 #ifdef MOZ_WEBRTC
11361 // Check if we have active PeerConnections
11362 if (win && win->HasActivePeerConnections()) {
11363 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11364 ("Save of %s blocked due to PeerConnection", uri.get()));
11365 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11366 ret = false;
11368 #endif // MOZ_WEBRTC
11370 // Don't save presentations for documents containing EME content, so that
11371 // CDMs reliably shutdown upon user navigation.
11372 if (ContainsEMEContent()) {
11373 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11374 ret = false;
11377 // Don't save presentations for documents containing MSE content, to
11378 // reduce memory usage.
11379 if (ContainsMSEContent()) {
11380 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11381 ("Save of %s blocked due to MSE use", uri.get()));
11382 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11383 ret = false;
11386 if (aIncludeSubdocuments && mSubDocuments) {
11387 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11388 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11389 Document* subdoc = entry->mSubDocument;
11391 uint32_t subDocBFCacheCombo = 0;
11392 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11393 bool canCache =
11394 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11395 true, allowUnloadListeners)
11396 : false;
11397 if (!canCache) {
11398 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11399 ("Save of %s blocked due to subdocument blocked", uri.get()));
11400 aBFCacheCombo |= subDocBFCacheCombo;
11401 ret = false;
11406 if (!mozilla::BFCacheInParent()) {
11407 // BFCache is currently not compatible with remote subframes (bug 1609324)
11408 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11409 for (auto& child : browsingContext->Children()) {
11410 if (!child->IsInProcess()) {
11411 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11412 ret = false;
11413 break;
11419 if (win) {
11420 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11421 #ifdef MOZ_WEBSPEECH
11422 if (globalWindow->HasActiveSpeechSynthesis()) {
11423 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11424 ("Save of %s blocked due to Speech use", uri.get()));
11425 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11426 ret = false;
11428 #endif
11429 if (globalWindow->HasUsedVR()) {
11430 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11431 ("Save of %s blocked due to having used VR", uri.get()));
11432 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11433 ret = false;
11436 if (win->HasActiveLocks()) {
11437 MOZ_LOG(
11438 gPageCacheLog, mozilla::LogLevel::Verbose,
11439 ("Save of %s blocked due to having active lock requests", uri.get()));
11440 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11441 ret = false;
11445 return ret;
11448 void Document::Destroy() {
11449 // The ContentViewer wants to release the document now. So, tell our content
11450 // to drop any references to the document so that it can be destroyed.
11451 if (mIsGoingAway) {
11452 return;
11455 ReportDocumentUseCounters();
11456 SetDevToolsWatchingDOMMutations(false);
11458 mIsGoingAway = true;
11460 ScriptLoader()->Destroy();
11461 SetScriptGlobalObject(nullptr);
11462 RemovedFromDocShell();
11464 bool oldVal = mInUnlinkOrDeletion;
11465 mInUnlinkOrDeletion = true;
11467 #ifdef DEBUG
11468 uint32_t oldChildCount = GetChildCount();
11469 #endif
11471 for (nsIContent* child = GetFirstChild(); child;
11472 child = child->GetNextSibling()) {
11473 child->DestroyContent();
11474 MOZ_ASSERT(child->GetParentNode() == this);
11476 MOZ_ASSERT(oldChildCount == GetChildCount());
11477 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11479 mInUnlinkOrDeletion = oldVal;
11481 mLayoutHistoryState = nullptr;
11483 if (mOriginalDocument) {
11484 mOriginalDocument->mLatestStaticClone = nullptr;
11487 if (IsStaticDocument()) {
11488 RemoveProperty(nsGkAtoms::printisfocuseddoc);
11489 RemoveProperty(nsGkAtoms::printselectionranges);
11492 // Shut down our external resource map. We might not need this for
11493 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11494 // tearing down all those frame trees right now is the right thing to do.
11495 mExternalResourceMap.Shutdown();
11497 // Manually break cycles via promise's global object pointer.
11498 mReadyForIdle = nullptr;
11499 mOrientationPendingPromise = nullptr;
11501 // To break cycles.
11502 mPreloadService.ClearAllPreloads();
11504 if (mDocumentL10n) {
11505 mDocumentL10n->Destroy();
11509 void Document::RemovedFromDocShell() {
11510 mEditingState = EditingState::eOff;
11512 if (mRemovedFromDocShell) return;
11514 mRemovedFromDocShell = true;
11515 NotifyActivityChanged();
11517 for (nsIContent* child = GetFirstChild(); child;
11518 child = child->GetNextSibling()) {
11519 child->SaveSubtreeState();
11522 nsIDocShell* docShell = GetDocShell();
11523 if (docShell) {
11524 docShell->SynchronizeLayoutHistoryState();
11528 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
11529 const {
11530 nsCOMPtr<nsILayoutHistoryState> state;
11531 if (!mScriptGlobalObject) {
11532 state = mLayoutHistoryState;
11533 } else {
11534 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
11535 if (docShell) {
11536 docShell->GetLayoutHistoryState(getter_AddRefs(state));
11540 return state.forget();
11543 void Document::EnsureOnloadBlocker() {
11544 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11545 // -- it's not ours.
11546 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
11547 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11548 if (loadGroup) {
11549 // Check first to see if mOnloadBlocker is in the loadgroup.
11550 nsCOMPtr<nsISimpleEnumerator> requests;
11551 loadGroup->GetRequests(getter_AddRefs(requests));
11553 bool hasMore = false;
11554 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11555 nsCOMPtr<nsISupports> elem;
11556 requests->GetNext(getter_AddRefs(elem));
11557 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11558 if (request && request == mOnloadBlocker) {
11559 return;
11563 // Not in the loadgroup, so add it.
11564 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11569 void Document::BlockOnload() {
11570 if (mDisplayDocument) {
11571 mDisplayDocument->BlockOnload();
11572 return;
11575 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11576 // -- it's not ours.
11577 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
11578 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11579 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11582 ++mOnloadBlockCount;
11585 void Document::UnblockOnload(bool aFireSync) {
11586 if (mDisplayDocument) {
11587 mDisplayDocument->UnblockOnload(aFireSync);
11588 return;
11591 --mOnloadBlockCount;
11593 if (mOnloadBlockCount == 0) {
11594 if (mScriptGlobalObject) {
11595 // Only manipulate the loadgroup in this case, because if
11596 // mScriptGlobalObject is null, it's not ours.
11597 if (aFireSync) {
11598 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
11599 ++mOnloadBlockCount;
11600 DoUnblockOnload();
11601 } else {
11602 PostUnblockOnloadEvent();
11604 } else if (mIsBeingUsedAsImage) {
11605 // To correctly unblock onload for a document that contains an SVG
11606 // image, we need to know when all of the SVG document's resources are
11607 // done loading, in a way comparable to |window.onload|. We fire this
11608 // event to indicate that the SVG should be considered fully loaded.
11609 // Because scripting is disabled on SVG-as-image documents, this event
11610 // is not accessible to content authors. (See bug 837315.)
11611 RefPtr<AsyncEventDispatcher> asyncDispatcher =
11612 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
11613 CanBubble::eNo, ChromeOnlyDispatch::eNo);
11614 asyncDispatcher->PostDOMEvent();
11619 class nsUnblockOnloadEvent : public Runnable {
11620 public:
11621 explicit nsUnblockOnloadEvent(Document* aDoc)
11622 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
11623 NS_IMETHOD Run() override {
11624 mDoc->DoUnblockOnload();
11625 return NS_OK;
11628 private:
11629 RefPtr<Document> mDoc;
11632 void Document::PostUnblockOnloadEvent() {
11633 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11634 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
11635 nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
11636 if (NS_SUCCEEDED(rv)) {
11637 // Stabilize block count so we don't post more events while this one is up
11638 ++mOnloadBlockCount;
11639 } else {
11640 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
11644 void Document::DoUnblockOnload() {
11645 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
11646 MOZ_ASSERT(mOnloadBlockCount != 0,
11647 "Shouldn't have a count of zero here, since we stabilized in "
11648 "PostUnblockOnloadEvent");
11650 --mOnloadBlockCount;
11652 if (mOnloadBlockCount != 0) {
11653 // We blocked again after the last unblock. Nothing to do here. We'll
11654 // post a new event when we unblock again.
11655 return;
11658 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11659 // -- it's not ours.
11660 if (mScriptGlobalObject) {
11661 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11662 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
11667 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
11668 for (nsIFrame* f = aFrame; f;
11669 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
11670 nsIContent* content = f->GetContent();
11671 if (!content || content->IsInNativeAnonymousSubtree()) continue;
11673 if (content->OwnerDoc() == this) {
11674 return content;
11676 // We must be in a subdocument so jump directly to the root frame.
11677 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
11678 // the containing document.
11679 f = f->PresContext()->GetPresShell()->GetRootFrame();
11682 return nullptr;
11685 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
11686 const nsAString& aType, bool aInFrameSwap,
11687 bool aPersisted, bool aOnlySystemGroup) {
11688 if (!aDispatchTarget) {
11689 return;
11692 PageTransitionEventInit init;
11693 init.mBubbles = true;
11694 init.mCancelable = true;
11695 init.mPersisted = aPersisted;
11696 init.mInFrameSwap = aInFrameSwap;
11698 RefPtr<PageTransitionEvent> event =
11699 PageTransitionEvent::Constructor(this, aType, init);
11701 event->SetTrusted(true);
11702 event->SetTarget(this);
11703 if (aOnlySystemGroup) {
11704 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
11706 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
11707 nullptr);
11710 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
11711 bool aOnlySystemGroup) {
11712 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11713 nsCString uri;
11714 if (GetDocumentURI()) {
11715 uri = GetDocumentURI()->GetSpecOrDefault();
11717 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11718 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
11721 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11722 MOZ_DIAGNOSTIC_ASSERT(
11723 inFrameLoaderSwap ==
11724 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11726 Element* root = GetRootElement();
11727 if (aPersisted && root) {
11728 // Send out notifications that our <link> elements are attached.
11729 RefPtr<nsContentList> links =
11730 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11732 uint32_t linkCount = links->Length(true);
11733 for (uint32_t i = 0; i < linkCount; ++i) {
11734 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
11738 // See Document
11739 if (!inFrameLoaderSwap) {
11740 if (aPersisted) {
11741 ImageTracker()->SetAnimatingState(true);
11744 // Set mIsShowing before firing events, in case those event handlers
11745 // move us around.
11746 mIsShowing = true;
11747 mVisible = true;
11749 UpdateVisibilityState();
11752 NotifyActivityChanged();
11754 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11755 aExternalResource.OnPageShow(aPersisted, nullptr);
11756 return CallState::Continue;
11758 EnumerateExternalResources(notifyExternal);
11760 if (mAnimationController) {
11761 mAnimationController->OnPageShow();
11764 if (!mIsBeingUsedAsImage) {
11765 // Dispatch observer notification to notify observers page is shown.
11766 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11767 if (os) {
11768 nsIPrincipal* principal = NodePrincipal();
11769 os->NotifyObservers(ToSupports(this),
11770 principal->IsSystemPrincipal() ? "chrome-page-shown"
11771 : "content-page-shown",
11772 nullptr);
11775 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11776 if (!target) {
11777 target = do_QueryInterface(GetWindow());
11779 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11780 aPersisted, aOnlySystemGroup);
11784 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11785 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11786 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11787 FullscreenEventType::Change, &aDocument, aTarget);
11788 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11789 std::move(pendingEvent));
11793 static void ClearPendingFullscreenRequests(Document* aDoc);
11795 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11796 bool aOnlySystemGroup) {
11797 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11798 nsCString uri;
11799 if (GetDocumentURI()) {
11800 uri = GetDocumentURI()->GetSpecOrDefault();
11802 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11803 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11806 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11807 MOZ_DIAGNOSTIC_ASSERT(
11808 inFrameLoaderSwap ==
11809 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11811 // Send out notifications that our <link> elements are detached,
11812 // but only if this is not a full unload.
11813 Element* root = GetRootElement();
11814 if (aPersisted && root) {
11815 RefPtr<nsContentList> links =
11816 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11818 uint32_t linkCount = links->Length(true);
11819 for (uint32_t i = 0; i < linkCount; ++i) {
11820 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
11824 if (mAnimationController) {
11825 mAnimationController->OnPageHide();
11828 if (!inFrameLoaderSwap) {
11829 if (aPersisted) {
11830 // We do not stop the animations (bug 1024343) when the page is refreshing
11831 // while being dragged out.
11832 ImageTracker()->SetAnimatingState(false);
11835 // Set mIsShowing before firing events, in case those event handlers
11836 // move us around.
11837 mIsShowing = false;
11838 mVisible = false;
11841 ExitPointerLock();
11843 if (!mIsBeingUsedAsImage) {
11844 // Dispatch observer notification to notify observers page is hidden.
11845 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11846 if (os) {
11847 nsIPrincipal* principal = NodePrincipal();
11848 os->NotifyObservers(ToSupports(this),
11849 principal->IsSystemPrincipal()
11850 ? "chrome-page-hidden"
11851 : "content-page-hidden",
11852 nullptr);
11855 // Now send out a PageHide event.
11856 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11857 if (!target) {
11858 target = do_QueryInterface(GetWindow());
11861 PageUnloadingEventTimeStamp timeStamp(this);
11862 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11863 aPersisted, aOnlySystemGroup);
11867 if (!inFrameLoaderSwap) {
11868 UpdateVisibilityState();
11871 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11872 aExternalResource.OnPageHide(aPersisted, nullptr);
11873 return CallState::Continue;
11875 EnumerateExternalResources(notifyExternal);
11876 NotifyActivityChanged();
11878 ClearPendingFullscreenRequests(this);
11879 if (Fullscreen()) {
11880 // If this document was fullscreen, we should exit fullscreen in this
11881 // doctree branch. This ensures that if the user navigates while in
11882 // fullscreen mode we don't leave its still visible ancestor documents
11883 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11884 // root document, as this will exit fullscreen in all the root's
11885 // descendant documents. Note that documents are removed from the
11886 // doctree by the time OnPageHide() is called, so we must store a
11887 // reference to the root (in Document::mFullscreenRoot) since we can't
11888 // just traverse the doctree to get the root.
11889 Document::ExitFullscreenInDocTree(this);
11891 // Since the document is removed from the doctree before OnPageHide() is
11892 // called, ExitFullscreen() can't traverse from the root down to *this*
11893 // document, so we must manually call CleanupFullscreenState() below too.
11894 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11895 // so we *must* call it after ExitFullscreen(), not before.
11896 // OnPageHide() is called in every hidden (i.e. descendant) document,
11897 // so calling CleanupFullscreenState() here will ensure all hidden
11898 // documents have their fullscreen state reset.
11899 CleanupFullscreenState();
11901 // The fullscreenchange event is to be queued in the refresh driver,
11902 // however a hidden page wouldn't trigger that again, so it makes no
11903 // sense to dispatch such event here.
11907 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11908 NS_ASSERTION(
11909 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11910 "mSubtreeModifiedTargets not cleared after dispatching?");
11911 ++mSubtreeModifiedDepth;
11912 if (aTarget) {
11913 // MayDispatchMutationEvent is often called just before this method,
11914 // so it has already appended the node to mSubtreeModifiedTargets.
11915 int32_t count = mSubtreeModifiedTargets.Count();
11916 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11917 mSubtreeModifiedTargets.AppendObject(aTarget);
11922 void Document::MutationEventDispatched(nsINode* aTarget) {
11923 --mSubtreeModifiedDepth;
11924 if (mSubtreeModifiedDepth == 0) {
11925 int32_t count = mSubtreeModifiedTargets.Count();
11926 if (!count) {
11927 return;
11930 nsPIDOMWindowInner* window = GetInnerWindow();
11931 if (window &&
11932 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11933 mSubtreeModifiedTargets.Clear();
11934 return;
11937 nsCOMArray<nsINode> realTargets;
11938 for (int32_t i = 0; i < count; ++i) {
11939 nsINode* possibleTarget = mSubtreeModifiedTargets[i];
11940 if (possibleTarget && possibleTarget->ChromeOnlyAccess()) {
11941 continue;
11944 nsINode* commonAncestor = nullptr;
11945 int32_t realTargetCount = realTargets.Count();
11946 for (int32_t j = 0; j < realTargetCount; ++j) {
11947 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
11948 possibleTarget, realTargets[j]);
11949 if (commonAncestor) {
11950 realTargets.ReplaceObjectAt(commonAncestor, j);
11951 break;
11954 if (!commonAncestor) {
11955 realTargets.AppendObject(possibleTarget);
11959 mSubtreeModifiedTargets.Clear();
11961 int32_t realTargetCount = realTargets.Count();
11962 for (int32_t k = 0; k < realTargetCount; ++k) {
11963 InternalMutationEvent mutation(true, eLegacySubtreeModified);
11964 (new AsyncEventDispatcher(realTargets[k], mutation))
11965 ->RunDOMEventWhenSafe();
11970 void Document::DestroyElementMaps() {
11971 #ifdef DEBUG
11972 mStyledLinksCleared = true;
11973 #endif
11974 mStyledLinks.Clear();
11975 // Notify ID change listeners before clearing the identifier map.
11976 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
11977 iter.Get()->ClearAndNotify();
11979 mIdentifierMap.Clear();
11980 mComposedShadowRoots.Clear();
11981 mResponsiveContent.Clear();
11982 IncrementExpandoGeneration(*this);
11985 void Document::RefreshLinkHrefs() {
11986 // Get a list of all links we know about. We will reset them, which will
11987 // remove them from the document, so we need a copy of what is in the
11988 // hashtable.
11989 const LinkArray linksToNotify = ToArray(mStyledLinks);
11991 // Reset all of our styled links.
11992 nsAutoScriptBlocker scriptBlocker;
11993 for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
11994 linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref());
11998 nsresult Document::CloneDocHelper(Document* clone) const {
11999 clone->mIsStaticDocument = mCreatingStaticClone;
12001 // Init document
12002 nsresult rv = clone->Init();
12003 NS_ENSURE_SUCCESS(rv, rv);
12005 if (mCreatingStaticClone) {
12006 if (mOriginalDocument) {
12007 clone->mOriginalDocument = mOriginalDocument;
12008 } else {
12009 clone->mOriginalDocument = const_cast<Document*>(this);
12011 clone->mOriginalDocument->mLatestStaticClone = clone;
12012 clone->mOriginalDocument->mStaticCloneCount++;
12014 nsCOMPtr<nsILoadGroup> loadGroup;
12016 // |mDocumentContainer| is the container of the document that is being
12017 // created and not the original container. See CreateStaticClone function().
12018 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
12019 if (docLoader) {
12020 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
12022 nsCOMPtr<nsIChannel> channel = GetChannel();
12023 nsCOMPtr<nsIURI> uri;
12024 if (channel) {
12025 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
12026 } else {
12027 uri = Document::GetDocumentURI();
12029 clone->mChannel = channel;
12030 if (uri) {
12031 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
12034 clone->mIsSrcdocDocument = mIsSrcdocDocument;
12035 clone->SetContainer(mDocumentContainer);
12037 // Setup the navigation time. This will be needed by any animations in the
12038 // document, even if they are only paused.
12039 MOZ_ASSERT(!clone->GetNavigationTiming(),
12040 "Navigation time was already set?");
12041 if (mTiming) {
12042 RefPtr<nsDOMNavigationTiming> timing =
12043 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
12044 clone->SetNavigationTiming(timing);
12046 clone->SetCsp(mCSP);
12049 // Now ensure that our clone has the same URI, base URI, and principal as us.
12050 // We do this after the mCreatingStaticClone block above, because that block
12051 // can set the base URI to an incorrect value in cases when base URI
12052 // information came from the channel. So we override explicitly, and do it
12053 // for all these properties, in case ResetToURI messes with any of the rest of
12054 // them.
12055 clone->SetDocumentURI(Document::GetDocumentURI());
12056 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
12057 clone->SetPrincipals(NodePrincipal(), mPartitionedPrincipal);
12058 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
12059 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
12060 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
12061 // when printed standalone via window.print() (where there won't be a parent
12062 // document to grab the URI from).
12063 clone->mDocumentBaseURI = GetDocBaseURI();
12064 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
12065 clone->mReferrerInfo =
12066 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
12067 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
12069 bool hasHadScriptObject = true;
12070 nsIScriptGlobalObject* scriptObject =
12071 GetScriptHandlingObject(hasHadScriptObject);
12072 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
12073 if (mCreatingStaticClone) {
12074 // If we're doing a static clone (print, print preview), then we're going to
12075 // be setting a scope object after the clone. It's better to set it only
12076 // once, so we don't do that here. However, we do want to act as if there is
12077 // a script handling object. So we set mHasHadScriptHandlingObject.
12078 clone->mHasHadScriptHandlingObject = true;
12079 } else if (scriptObject) {
12080 clone->SetScriptHandlingObject(scriptObject);
12081 } else {
12082 clone->SetScopeObject(GetScopeObject());
12084 // Make the clone a data document
12085 clone->SetLoadedAsData(
12086 true,
12087 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
12089 // Misc state
12091 // State from Document
12092 clone->mCharacterSet = mCharacterSet;
12093 clone->mCharacterSetSource = mCharacterSetSource;
12094 clone->SetCompatibilityMode(mCompatMode);
12095 clone->mBidiOptions = mBidiOptions;
12096 clone->mContentLanguage = mContentLanguage;
12097 clone->SetContentType(GetContentTypeInternal());
12098 clone->mSecurityInfo = mSecurityInfo;
12100 // State from Document
12101 clone->mType = mType;
12102 clone->mXMLDeclarationBits = mXMLDeclarationBits;
12103 clone->mBaseTarget = mBaseTarget;
12105 return NS_OK;
12108 void Document::NotifyLoading(bool aNewParentIsLoading,
12109 const ReadyState& aCurrentState,
12110 ReadyState aNewState) {
12111 // Mirror the top-level loading state down to all subdocuments
12112 bool was_loading = mAncestorIsLoading ||
12113 aCurrentState == READYSTATE_LOADING ||
12114 aCurrentState == READYSTATE_INTERACTIVE;
12115 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
12116 aNewState == READYSTATE_INTERACTIVE; // new value for state
12117 bool set_load_state = was_loading != is_loading;
12119 MOZ_LOG(
12120 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12121 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12122 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12123 "set_load_state: %d",
12124 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12125 (int)aNewState, was_loading, is_loading, set_load_state));
12127 mAncestorIsLoading = aNewParentIsLoading;
12128 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12129 // Tell our innerwindow (and thus TimeoutManager)
12130 nsPIDOMWindowInner* inner = GetInnerWindow();
12131 if (inner) {
12132 inner->SetActiveLoadingState(is_loading);
12134 BrowsingContext* context = GetBrowsingContext();
12135 if (context) {
12136 // Don't use PreOrderWalk to mirror this down; go down one level as a
12137 // time so we can set mAncestorIsLoading and take into account the
12138 // readystates of the subdocument. In the child process it will call
12139 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12140 // iterate it's children
12141 for (auto& child : context->Children()) {
12142 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12143 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12144 // Setting ancestor loading on a discarded browsing context has no
12145 // effect.
12146 Unused << child->SetAncestorLoading(is_loading);
12152 void Document::SetReadyStateInternal(ReadyState aReadyState,
12153 bool aUpdateTimingInformation) {
12154 if (aReadyState == READYSTATE_UNINITIALIZED) {
12155 // Transition back to uninitialized happens only to keep assertions happy
12156 // right before readyState transitions to something else. Make this
12157 // transition undetectable by Web content.
12158 mReadyState = aReadyState;
12159 return;
12162 if (IsTopLevelContentDocument()) {
12163 if (aReadyState == READYSTATE_LOADING) {
12164 AddToplevelLoadingDocument(this);
12165 } else if (aReadyState == READYSTATE_COMPLETE) {
12166 RemoveToplevelLoadingDocument(this);
12170 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12171 mLoadingTimeStamp = TimeStamp::Now();
12173 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12174 mReadyState = aReadyState;
12175 if (aUpdateTimingInformation && mTiming) {
12176 switch (aReadyState) {
12177 case READYSTATE_LOADING:
12178 mTiming->NotifyDOMLoading(GetDocumentURI());
12179 break;
12180 case READYSTATE_INTERACTIVE:
12181 mTiming->NotifyDOMInteractive(GetDocumentURI());
12182 break;
12183 case READYSTATE_COMPLETE:
12184 mTiming->NotifyDOMComplete(GetDocumentURI());
12185 break;
12186 default:
12187 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12188 break;
12191 // At the time of loading start, we don't have timing object, record time.
12193 if (READYSTATE_INTERACTIVE == aReadyState &&
12194 NodePrincipal()->IsSystemPrincipal()) {
12195 if (!mXULPersist) {
12196 mXULPersist = new XULPersist(this);
12197 mXULPersist->Init();
12199 if (!mChromeObserver) {
12200 mChromeObserver = new ChromeObserver(this);
12201 mChromeObserver->Init();
12205 if (aUpdateTimingInformation) {
12206 RecordNavigationTiming(aReadyState);
12209 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
12210 this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12211 asyncDispatcher->RunDOMEventWhenSafe();
12214 void Document::GetReadyState(nsAString& aReadyState) const {
12215 switch (mReadyState) {
12216 case READYSTATE_LOADING:
12217 aReadyState.AssignLiteral(u"loading");
12218 break;
12219 case READYSTATE_INTERACTIVE:
12220 aReadyState.AssignLiteral(u"interactive");
12221 break;
12222 case READYSTATE_COMPLETE:
12223 aReadyState.AssignLiteral(u"complete");
12224 break;
12225 default:
12226 aReadyState.AssignLiteral(u"uninitialized");
12230 void Document::SuppressEventHandling(uint32_t aIncrease) {
12231 mEventsSuppressed += aIncrease;
12232 if (mEventsSuppressed == aIncrease) {
12233 WindowGlobalChild* wgc = GetWindowGlobalChild();
12234 if (wgc) {
12235 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12238 UpdateFrameRequestCallbackSchedulingState();
12239 for (uint32_t i = 0; i < aIncrease; ++i) {
12240 ScriptLoader()->AddExecuteBlocker();
12243 auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
12244 aSubDoc.SuppressEventHandling(aIncrease);
12245 return CallState::Continue;
12248 EnumerateSubDocuments(suppressInSubDoc);
12251 void Document::NotifyAbortedLoad() {
12252 // If we still have outstanding work blocking DOMContentLoaded,
12253 // then don't try to change the readystate now, but wait until
12254 // they finish and then do so.
12255 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12256 mSetCompleteAfterDOMContentLoaded = true;
12257 return;
12260 // Otherwise we're fully done at this point, so set the
12261 // readystate to complete.
12262 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12263 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12267 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents(
12268 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) {
12269 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
12270 if (MOZ_UNLIKELY(!fm)) {
12271 return;
12274 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments);
12275 for (uint32_t i = 0; i < documents.Length(); ++i) {
12276 nsCOMPtr<Document> document = std::move(documents[i]);
12277 // NB: Don't bother trying to fire delayed events on documents that were
12278 // closed before this event ran.
12279 if (!document->EventHandlingSuppressed()) {
12280 fm->FireDelayedEvents(document);
12281 RefPtr<PresShell> presShell = document->GetPresShell();
12282 if (presShell) {
12283 // Only fire events for active documents.
12284 bool fire = aFireEvents && document->GetInnerWindow() &&
12285 document->GetInnerWindow()->IsCurrentInnerWindow();
12286 presShell->FireOrClearDelayedEvents(fire);
12288 document->FireOrClearPostMessageEvents(aFireEvents);
12293 void Document::PreloadPictureClosed() {
12294 MOZ_ASSERT(mPreloadPictureDepth > 0);
12295 mPreloadPictureDepth--;
12296 if (mPreloadPictureDepth == 0) {
12297 mPreloadPictureFoundSource.SetIsVoid(true);
12301 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12302 const nsAString& aSizesAttr,
12303 const nsAString& aTypeAttr,
12304 const nsAString& aMediaAttr) {
12305 // Nested pictures are not valid syntax, so while we'll eventually load them,
12306 // it's not worth tracking sources mixed between nesting levels to preload
12307 // them effectively.
12308 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12309 // <picture> selects the first matching source, so if this returns a URI we
12310 // needn't consider new sources until a new <picture> is encountered.
12311 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12312 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12313 aMediaAttr, mPreloadPictureFoundSource);
12314 if (found && mPreloadPictureFoundSource.IsVoid()) {
12315 // Found an empty source, which counts
12316 mPreloadPictureFoundSource.SetIsVoid(false);
12321 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12322 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12323 const nsAString& aSizesAttr, bool* aIsImgSet) {
12324 nsString sourceURL;
12325 bool isImgSet;
12326 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12327 // We're in a <picture> element and found a URI from a source previous to
12328 // this image, use it.
12329 sourceURL = mPreloadPictureFoundSource;
12330 isImgSet = true;
12331 } else {
12332 // Otherwise try to use this <img> as a source
12333 HTMLImageElement::SelectSourceForTagWithAttrs(
12334 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12335 VoidString(), sourceURL);
12336 isImgSet = !aSrcsetAttr.IsEmpty();
12339 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12340 if (sourceURL.IsEmpty()) {
12341 return nullptr;
12344 // Construct into URI using passed baseURI (the parser may know of base URI
12345 // changes that have not reached us)
12346 nsresult rv;
12347 nsCOMPtr<nsIURI> uri;
12348 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12349 this, aBaseURI);
12350 if (NS_FAILED(rv)) {
12351 return nullptr;
12354 *aIsImgSet = isImgSet;
12356 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12357 // this this <picture> share the same <sources> (though this is not valid per
12358 // spec)
12359 return uri.forget();
12362 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12363 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12364 bool aLinkPreload) {
12365 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12366 nsIRequest::LOAD_RECORD_START_REQUEST_DELAY |
12367 nsContentUtils::CORSModeToLoadImageFlags(
12368 Element::StringToCORSMode(aCrossOriginAttr));
12370 nsContentPolicyType policyType =
12371 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12372 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12374 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12375 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12377 RefPtr<imgRequestProxy> request;
12378 nsresult rv = nsContentUtils::LoadImage(
12379 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12380 nullptr /* no observer */, loadFlags,
12381 aLinkPreload ? u"link"_ns : u"img"_ns, getter_AddRefs(request),
12382 policyType, false /* urgent */, aLinkPreload);
12384 // Pin image-reference to avoid evicting it from the img-cache before
12385 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12386 // unlink
12387 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12388 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12392 void Document::MaybePreLoadImage(nsIURI* aUri,
12393 const nsAString& aCrossOriginAttr,
12394 ReferrerPolicyEnum aReferrerPolicy,
12395 bool aIsImgSet, bool aLinkPreload,
12396 const TimeStamp& aInitTimestamp) {
12397 if (aLinkPreload) {
12398 // Check if the image was already preloaded in this document to avoid
12399 // duplicate preloading.
12400 PreloadHashKey key = PreloadHashKey::CreateAsImage(
12401 aUri, NodePrincipal(),
12402 dom::Element::StringToCORSMode(aCrossOriginAttr));
12403 if (!mPreloadService.PreloadExists(key)) {
12404 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12405 aLinkPreload);
12407 return;
12410 // Early exit if the img is already present in the img-cache
12411 // which indicates that the "real" load has already started and
12412 // that we shouldn't preload it.
12413 if (nsContentUtils::IsImageInCache(aUri, this)) {
12414 return;
12417 #ifdef NIGHTLY_BUILD
12418 Telemetry::Accumulate(
12419 Telemetry::DOCUMENT_PRELOAD_IMAGE_ASYNCOPEN_DELAY,
12420 static_cast<uint32_t>(
12421 (TimeStamp::Now() - aInitTimestamp).ToMilliseconds()));
12422 #endif
12424 // Image not in cache - trigger preload
12425 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12426 aLinkPreload);
12429 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12430 NS_MutateURI mutator(aOrigURI);
12431 if (NS_FAILED(mutator.GetStatus())) {
12432 return;
12435 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12436 // which ignores the path and uses only the origin. The other is for the
12437 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12438 // non-Anonymous preconnects create different connections on the wire and
12439 // therefore should not be considred duplicates of each other and we
12440 // normalize the path before putting it in the hash to accomplish that.
12442 if (aCORSMode == CORS_ANONYMOUS) {
12443 mutator.SetPathQueryRef("/anonymous"_ns);
12444 } else {
12445 mutator.SetPathQueryRef("/"_ns);
12448 nsCOMPtr<nsIURI> uri;
12449 nsresult rv = mutator.Finalize(uri);
12450 if (NS_FAILED(rv)) {
12451 return;
12454 const bool existingEntryFound =
12455 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12456 if (entry) {
12457 return true;
12459 entry.Insert(true);
12460 return false;
12462 if (existingEntryFound) {
12463 return;
12466 nsCOMPtr<nsISpeculativeConnect> speculator(
12467 do_QueryInterface(nsContentUtils::GetIOService()));
12468 if (!speculator) {
12469 return;
12472 if (aCORSMode == CORS_ANONYMOUS) {
12473 speculator->SpeculativeAnonymousConnect(uri, NodePrincipal(), nullptr);
12474 } else {
12475 speculator->SpeculativeConnect(uri, NodePrincipal(), nullptr);
12479 void Document::ForgetImagePreload(nsIURI* aURI) {
12480 // Checking count is faster than hashing the URI in the common
12481 // case of empty table.
12482 if (mPreloadingImages.Count() != 0) {
12483 nsCOMPtr<imgIRequest> req;
12484 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12485 if (req) {
12486 // Make sure to cancel the request so imagelib knows it's gone.
12487 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12492 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates,
12493 bool aNotify) {
12494 const DocumentState oldStates = mDocumentState;
12495 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12496 DocumentState::ALL_LOCALEDIR_BITS)) {
12497 mDocumentState &= ~DocumentState::ALL_LOCALEDIR_BITS;
12498 if (IsDocumentRightToLeft()) {
12499 mDocumentState |= DocumentState::RTL_LOCALE;
12500 } else {
12501 mDocumentState |= DocumentState::LTR_LOCALE;
12505 if (aMaybeChangedStates.HasAtLeastOneOfStates(DocumentState::LWTHEME)) {
12506 if (ComputeDocumentLWTheme()) {
12507 mDocumentState |= DocumentState::LWTHEME;
12508 } else {
12509 mDocumentState &= ~DocumentState::LWTHEME;
12513 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) {
12514 if (IsTopLevelWindowInactive()) {
12515 mDocumentState |= DocumentState::WINDOW_INACTIVE;
12516 } else {
12517 mDocumentState &= ~DocumentState::WINDOW_INACTIVE;
12521 const DocumentState changedStates = oldStates ^ mDocumentState;
12522 if (aNotify && !changedStates.IsEmpty()) {
12523 if (PresShell* ps = GetObservingPresShell()) {
12524 ps->DocumentStatesChanged(changedStates);
12529 namespace {
12532 * Stub for LoadSheet(), since all we want is to get the sheet into
12533 * the CSSLoader's style cache
12535 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12536 ~StubCSSLoaderObserver() = default;
12538 public:
12539 NS_IMETHOD
12540 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12541 NS_DECL_ISUPPORTS
12543 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12545 } // namespace
12547 SheetPreloadStatus Document::PreloadStyle(
12548 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12549 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aIntegrity,
12550 css::StylePreloadKind aKind) {
12551 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12553 // The CSSLoader will retain this object after we return.
12554 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12556 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12557 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12559 // Charset names are always ASCII.
12560 auto result = CSSLoader()->LoadSheet(
12561 uri, aKind, aEncoding, referrerInfo, obs,
12562 Element::StringToCORSMode(aCrossOriginAttr), aIntegrity);
12563 if (result.isErr()) {
12564 return SheetPreloadStatus::Errored;
12566 RefPtr<StyleSheet> sheet = result.unwrap();
12567 if (sheet->IsComplete()) {
12568 return SheetPreloadStatus::AlreadyComplete;
12570 return SheetPreloadStatus::InProgress;
12573 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12574 return CSSLoader()
12575 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12576 .unwrapOr(nullptr);
12579 void Document::ResetDocumentDirection() {
12580 if (!nsContentUtils::IsChromeDoc(this)) {
12581 return;
12583 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true);
12586 bool Document::IsDocumentRightToLeft() {
12587 if (!nsContentUtils::IsChromeDoc(this)) {
12588 return false;
12590 // setting the localedir attribute on the root element forces a
12591 // specific direction for the document.
12592 Element* element = GetRootElement();
12593 if (element) {
12594 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12595 nullptr};
12596 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12597 strings, eCaseMatters)) {
12598 case 0:
12599 return false;
12600 case 1:
12601 return true;
12602 default:
12603 break; // otherwise, not a valid value, so fall through
12607 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12608 !mDocumentURI->SchemeIs("resource")) {
12609 return false;
12612 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12615 class nsDelayedEventDispatcher : public Runnable {
12616 public:
12617 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12618 : mozilla::Runnable("nsDelayedEventDispatcher"),
12619 mDocuments(std::move(aDocuments)) {}
12620 virtual ~nsDelayedEventDispatcher() = default;
12622 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
12623 // bug 1535398.
12624 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
12625 FireOrClearDelayedEvents(std::move(mDocuments), true);
12626 return NS_OK;
12629 private:
12630 nsTArray<nsCOMPtr<Document>> mDocuments;
12633 static void GetAndUnsuppressSubDocuments(
12634 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12635 if (aDocument.EventHandlingSuppressed() > 0) {
12636 aDocument.DecreaseEventSuppression();
12637 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12639 aDocuments.AppendElement(&aDocument);
12640 auto recurse = [&aDocuments](Document& aSubDoc) {
12641 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12642 return CallState::Continue;
12644 aDocument.EnumerateSubDocuments(recurse);
12647 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12648 nsTArray<nsCOMPtr<Document>> documents;
12649 GetAndUnsuppressSubDocuments(*this, documents);
12651 for (nsCOMPtr<Document>& doc : documents) {
12652 if (!doc->EventHandlingSuppressed()) {
12653 WindowGlobalChild* wgc = doc->GetWindowGlobalChild();
12654 if (wgc) {
12655 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12658 MOZ_ASSERT(NS_IsMainThread());
12659 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12660 std::move(doc->mSuspendedQueues);
12661 for (net::ChannelEventQueue* queue : queues) {
12662 queue->Resume();
12665 // If there have been any events driven by the refresh driver which were
12666 // delayed due to events being suppressed in this document, make sure
12667 // there is a refresh scheduled soon so the events will run.
12668 if (doc->mHasDelayedRefreshEvent) {
12669 doc->mHasDelayedRefreshEvent = false;
12671 if (doc->mPresShell) {
12672 nsRefreshDriver* rd =
12673 doc->mPresShell->GetPresContext()->RefreshDriver();
12674 rd->RunDelayedEventsSoon();
12680 if (aFireEvents) {
12681 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12682 nsCOMPtr<nsIRunnable> ded =
12683 new nsDelayedEventDispatcher(std::move(documents));
12684 Dispatch(TaskCategory::Other, ded.forget());
12685 } else {
12686 FireOrClearDelayedEvents(std::move(documents), false);
12690 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12691 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12694 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12695 MOZ_ASSERT(NS_IsMainThread());
12696 MOZ_ASSERT(EventHandlingSuppressed());
12697 mSuspendedQueues.AppendElement(aQueue);
12700 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12701 MOZ_ASSERT(NS_IsMainThread());
12703 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12704 mSuspendedPostMessageEvents.AppendElement(aEvent);
12705 return true;
12707 return false;
12710 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12711 nsTArray<RefPtr<PostMessageEvent>> events =
12712 std::move(mSuspendedPostMessageEvents);
12714 if (aFireEvents) {
12715 for (PostMessageEvent* event : events) {
12716 event->Run();
12721 void Document::SetSuppressedEventListener(EventListener* aListener) {
12722 mSuppressedEventListener = aListener;
12723 auto setOnSubDocs = [&](Document& aDocument) {
12724 aDocument.SetSuppressedEventListener(aListener);
12725 return CallState::Continue;
12727 EnumerateSubDocuments(setOnSubDocs);
12730 bool Document::IsActive() const {
12731 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
12732 !GetBrowsingContext()->IsInBFCache();
12735 nsISupports* Document::GetCurrentContentSink() {
12736 return mParser ? mParser->GetContentSink() : nullptr;
12739 Document* Document::GetTemplateContentsOwner() {
12740 if (!mTemplateContentsOwner) {
12741 bool hasHadScriptObject = true;
12742 nsIScriptGlobalObject* scriptObject =
12743 GetScriptHandlingObject(hasHadScriptObject);
12745 nsCOMPtr<Document> document;
12746 nsresult rv = NS_NewDOMDocument(
12747 getter_AddRefs(document),
12748 u""_ns, // aNamespaceURI
12749 u""_ns, // aQualifiedName
12750 nullptr, // aDoctype
12751 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12752 true, // aLoadedAsData
12753 scriptObject, // aEventObject
12754 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12755 NS_ENSURE_SUCCESS(rv, nullptr);
12757 mTemplateContentsOwner = document;
12758 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12760 if (!scriptObject) {
12761 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12764 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12766 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12767 // that it is the template contents owner of nested template elements.
12768 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12771 return mTemplateContentsOwner;
12774 static already_AddRefed<nsPIDOMWindowOuter> FindTopWindowForElement(
12775 Element* element) {
12776 Document* document = element->OwnerDoc();
12777 if (!document) {
12778 return nullptr;
12781 nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow();
12782 if (!window) {
12783 return nullptr;
12786 // Trying to find the top window (equivalent to window.top).
12787 if (nsCOMPtr<nsPIDOMWindowOuter> top = window->GetInProcessTop()) {
12788 window = std::move(top);
12790 return window.forget();
12794 * nsAutoFocusEvent is used to dispatch a focus event for an
12795 * nsGenericHTMLFormElement with the autofocus attribute enabled.
12797 class nsAutoFocusEvent : public Runnable {
12798 public:
12799 explicit nsAutoFocusEvent(nsCOMPtr<Element>&& aElement,
12800 nsCOMPtr<nsPIDOMWindowOuter>&& aTopWindow)
12801 : mozilla::Runnable("nsAutoFocusEvent"),
12802 mElement(std::move(aElement)),
12803 mTopWindow(std::move(aTopWindow)) {}
12805 NS_IMETHOD Run() override {
12806 nsCOMPtr<nsPIDOMWindowOuter> currentTopWindow =
12807 FindTopWindowForElement(mElement);
12808 if (currentTopWindow != mTopWindow) {
12809 // The element's top window changed from when the event was queued.
12810 // Don't take away focus from an unrelated window.
12811 return NS_OK;
12814 if (Document* doc = mTopWindow->GetExtantDoc()) {
12815 if (doc->IsAutoFocusFired()) {
12816 return NS_OK;
12818 doc->SetAutoFocusFired();
12821 // Don't steal focus from the user.
12822 if (mTopWindow->GetFocusedElement()) {
12823 return NS_OK;
12826 FocusOptions options;
12827 ErrorResult rv;
12828 mElement->Focus(options, CallerType::System, rv);
12829 return rv.StealNSResult();
12832 private:
12833 nsCOMPtr<Element> mElement;
12834 nsCOMPtr<nsPIDOMWindowOuter> mTopWindow;
12837 void Document::SetAutoFocusElement(Element* aAutoFocusElement) {
12838 if (mAutoFocusFired) {
12839 // Too late.
12840 return;
12843 if (mAutoFocusElement) {
12844 // The spec disallows multiple autofocus elements, so we consider only the
12845 // first one to preserve the old behavior.
12846 return;
12849 mAutoFocusElement = do_GetWeakReference(aAutoFocusElement);
12850 TriggerAutoFocus();
12853 void Document::SetAutoFocusFired() { mAutoFocusFired = true; }
12855 bool Document::IsAutoFocusFired() { return mAutoFocusFired; }
12857 void Document::TriggerAutoFocus() {
12858 if (mAutoFocusFired) {
12859 return;
12862 if (!mPresShell || !mPresShell->DidInitialize()) {
12863 // Delay autofocus until frames are constructed so that we don't thrash
12864 // style and layout calculations.
12865 return;
12868 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(mAutoFocusElement);
12869 if (autoFocusElement && autoFocusElement->OwnerDoc() == this) {
12870 nsCOMPtr<nsPIDOMWindowOuter> topWindow =
12871 FindTopWindowForElement(autoFocusElement);
12872 if (!topWindow) {
12873 return;
12876 // NOTE: This may be removed in the future since the spec technically
12877 // allows autofocus after load.
12878 nsCOMPtr<Document> topDoc = topWindow->GetExtantDoc();
12879 if (topDoc &&
12880 topDoc->GetReadyStateEnum() == Document::READYSTATE_COMPLETE) {
12881 return;
12884 nsCOMPtr<nsIRunnable> event =
12885 new nsAutoFocusEvent(std::move(autoFocusElement), topWindow.forget());
12886 nsresult rv = NS_DispatchToCurrentThread(event.forget());
12887 NS_ENSURE_SUCCESS_VOID(rv);
12891 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
12892 if (!aDocumentURI) {
12893 return;
12896 nsAutoCString ref;
12898 // Since all URI's that pass through here aren't URL's we can't
12899 // rely on the nsIURI implementation for providing a way for
12900 // finding the 'ref' part of the URI, we'll haveto revert to
12901 // string routines for finding the data past '#'
12903 nsresult rv = aDocumentURI->GetSpec(ref);
12904 if (NS_FAILED(rv)) {
12905 Unused << aDocumentURI->GetRef(mScrollToRef);
12906 return;
12909 nsReadingIterator<char> start, end;
12911 ref.BeginReading(start);
12912 ref.EndReading(end);
12914 if (FindCharInReadable('#', start, end)) {
12915 ++start; // Skip over the '#'
12917 mScrollToRef = Substring(start, end);
12921 void Document::ScrollToRef() {
12922 if (mScrolledToRefAlready) {
12923 RefPtr<PresShell> presShell = GetPresShell();
12924 if (presShell) {
12925 presShell->ScrollToAnchor();
12927 return;
12930 if (mScrollToRef.IsEmpty()) {
12931 return;
12934 RefPtr<PresShell> presShell = GetPresShell();
12935 if (presShell) {
12936 nsresult rv = NS_ERROR_FAILURE;
12937 // We assume that the bytes are in UTF-8, as it says in the spec:
12938 // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
12939 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
12940 // Check an empty string which might be caused by the UTF-8 conversion
12941 if (!ref.IsEmpty()) {
12942 // Note that GoToAnchor will handle flushing layout as needed.
12943 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
12944 } else {
12945 rv = NS_ERROR_FAILURE;
12948 if (NS_FAILED(rv)) {
12949 nsAutoCString buff;
12950 const bool unescaped =
12951 NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
12952 /*aFlags =*/0, buff);
12954 // This attempt is only necessary if characters were unescaped.
12955 if (unescaped) {
12956 NS_ConvertUTF8toUTF16 utf16Str(buff);
12957 if (!utf16Str.IsEmpty()) {
12958 rv = presShell->GoToAnchor(utf16Str,
12959 mChangeScrollPosWhenScrollingToRef);
12963 // If UTF-8 URI failed then try to assume the string as a
12964 // document's charset.
12965 if (NS_FAILED(rv)) {
12966 const Encoding* encoding = GetDocumentCharacterSet();
12967 rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
12968 ref);
12969 if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
12970 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
12974 if (NS_SUCCEEDED(rv)) {
12975 mScrolledToRefAlready = true;
12980 void Document::RegisterActivityObserver(nsISupports* aSupports) {
12981 if (!mActivityObservers) {
12982 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
12984 mActivityObservers->Insert(aSupports);
12987 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
12988 if (!mActivityObservers) {
12989 return false;
12991 return mActivityObservers->EnsureRemoved(aSupports);
12994 void Document::EnumerateActivityObservers(
12995 ActivityObserverEnumerator aEnumerator) {
12996 if (!mActivityObservers) {
12997 return;
13000 const auto keyArray =
13001 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
13002 for (auto& observer : keyArray) {
13003 aEnumerator(observer.get());
13007 void Document::RegisterPendingLinkUpdate(Link* aLink) {
13008 if (aLink->HasPendingLinkUpdate()) {
13009 return;
13012 aLink->SetHasPendingLinkUpdate();
13014 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
13015 nsCOMPtr<nsIRunnable> event =
13016 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
13017 &Document::FlushPendingLinkUpdates);
13018 // Do this work in a second in the worst case.
13019 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
13020 EventQueuePriority::Idle);
13021 if (NS_FAILED(rv)) {
13022 // If during shutdown posting a runnable doesn't succeed, we probably
13023 // don't need to update link states.
13024 return;
13026 mHasLinksToUpdateRunnable = true;
13029 mLinksToUpdate.InfallibleAppend(aLink);
13032 void Document::FlushPendingLinkUpdates() {
13033 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
13034 MOZ_ASSERT(mHasLinksToUpdateRunnable);
13035 mHasLinksToUpdateRunnable = false;
13037 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
13038 mFlushingPendingLinkUpdates = true;
13040 while (!mLinksToUpdate.IsEmpty()) {
13041 LinksToUpdateList links(std::move(mLinksToUpdate));
13042 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
13043 Link* link = iter.Get();
13044 Element* element = link->GetElement();
13045 if (element->OwnerDoc() == this) {
13046 link->ClearHasPendingLinkUpdate();
13047 if (element->IsInComposedDoc()) {
13048 element->UpdateLinkState(link->LinkState());
13056 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
13057 * which is a node in the original document from which aStaticClone was cloned.
13059 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
13060 Document& aStaticClone) {
13061 MOZ_ASSERT(aOrigNode);
13063 // Selections in anonymous subtrees aren't supported.
13064 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
13065 return nullptr;
13068 // If the node is disconnected, this is a bug in the selection code, but it
13069 // can happen with shadow DOM so handle it.
13070 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
13071 return nullptr;
13074 AutoTArray<Maybe<uint32_t>, 32> indexArray;
13075 const nsINode* current = aOrigNode;
13076 while (const nsINode* parent = current->GetParentNode()) {
13077 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
13078 NS_ENSURE_TRUE(index.isSome(), nullptr);
13079 indexArray.AppendElement(std::move(index));
13080 current = parent;
13082 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
13083 nsINode* correspondingNode = [&]() -> nsINode* {
13084 if (current->IsDocument()) {
13085 return &aStaticClone;
13087 const auto* shadow = ShadowRoot::FromNode(*current);
13088 if (!shadow) {
13089 return nullptr;
13091 nsINode* correspondingHost =
13092 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
13093 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
13094 return nullptr;
13096 return correspondingHost->AsElement()->GetShadowRoot();
13097 }();
13099 if (NS_WARN_IF(!correspondingNode)) {
13100 return nullptr;
13102 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
13103 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
13104 NS_ENSURE_TRUE(correspondingNode, nullptr);
13106 return correspondingNode;
13110 * Caches the selection ranges from the source document onto the static clone in
13111 * case the "Print Selection Only" functionality is invoked.
13113 * Note that we cannot use the selection obtained from GetOriginalDocument()
13114 * since that selection may have mutated after the print was invoked.
13116 * Note also that because nsRange objects point into a specific document's
13117 * nodes, we cannot reuse an array of nsRange objects across multiple static
13118 * clone documents. For that reason we cache a new array of ranges on each
13119 * static clone that we create.
13121 * TODO(emilio): This can be simplified once we don't re-clone from static
13122 * documents.
13124 * @param aSourceDoc the document from which we are caching selection ranges
13125 * @param aStaticClone the document that will hold the cache
13126 * @return true if a selection range was cached
13128 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13129 Document& aStaticClone) {
13130 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13131 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc));
13132 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13134 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13136 // When the user opts to "Print Selection Only", the print code prefers any
13137 // selection in the static clone corresponding to the focused frame. If this
13138 // is that static clone, flag it for the printing code:
13139 const bool isFocusedDoc = [&] {
13140 if (sourceDocIsStatic) {
13141 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc));
13143 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow();
13144 if (!window) {
13145 return false;
13147 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
13148 if (!rootWindow) {
13149 return false;
13151 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
13152 nsFocusManager::GetFocusedDescendant(rootWindow,
13153 nsFocusManager::eIncludeAllDescendants,
13154 getter_AddRefs(focusedWindow));
13155 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc;
13156 }();
13157 if (isFocusedDoc) {
13158 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc,
13159 reinterpret_cast<void*>(true));
13162 const Selection* origSelection = nullptr;
13163 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13165 if (sourceDocIsStatic) {
13166 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13167 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13168 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13169 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13172 if (!origSelection && !origRanges) {
13173 return;
13176 const uint32_t rangeCount =
13177 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13178 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13180 for (const uint32_t i : IntegerRange(rangeCount)) {
13181 MOZ_ASSERT_IF(!sourceDocIsStatic,
13182 origSelection->RangeCount() == rangeCount);
13183 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13184 : origSelection->GetRangeAt(i);
13185 MOZ_ASSERT(range);
13186 nsINode* startContainer = range->GetStartContainer();
13187 nsINode* endContainer = range->GetEndContainer();
13189 if (!startContainer || !endContainer) {
13190 continue;
13193 nsINode* startNode =
13194 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13195 nsINode* endNode =
13196 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13198 if (NS_WARN_IF(!startNode || !endNode)) {
13199 continue;
13202 RefPtr<nsRange> clonedRange =
13203 nsRange::Create(startNode, range->StartOffset(), endNode,
13204 range->EndOffset(), IgnoreErrors());
13205 if (clonedRange && !clonedRange->Collapsed()) {
13206 printRanges->AppendElement(std::move(clonedRange));
13210 if (printRanges->IsEmpty()) {
13211 return;
13214 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13215 printRanges.release(),
13216 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13219 already_AddRefed<Document> Document::CreateStaticClone(
13220 nsIDocShell* aCloneContainer, nsIContentViewer* aViewer,
13221 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13222 MOZ_ASSERT(!mCreatingStaticClone);
13223 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13224 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13226 mCreatingStaticClone = true;
13227 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13228 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13230 auto raii = MakeScopeExit([&] {
13231 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13232 mCreatingStaticClone = false;
13235 // Make document use different container during cloning.
13237 // FIXME(emilio): Why is this needed?
13238 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13239 SetContainer(nsDocShell::Cast(aCloneContainer));
13240 IgnoredErrorResult rv;
13241 nsCOMPtr<nsINode> clonedNode = this->CloneNode(true, rv);
13242 SetContainer(originalShell);
13243 if (rv.Failed()) {
13244 return nullptr;
13247 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13248 if (!clonedDoc) {
13249 return nullptr;
13252 size_t sheetsCount = SheetCount();
13253 for (size_t i = 0; i < sheetsCount; ++i) {
13254 RefPtr<StyleSheet> sheet = SheetAt(i);
13255 if (sheet) {
13256 if (sheet->IsApplicable()) {
13257 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13258 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13259 if (clonedSheet) {
13260 clonedDoc->AddStyleSheet(clonedSheet);
13265 clonedDoc->CloneAdoptedSheetsFrom(*this);
13267 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13268 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13269 for (StyleSheet* sheet : sheets) {
13270 if (sheet->IsApplicable()) {
13271 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13272 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13273 if (clonedSheet) {
13274 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13275 clonedSheet);
13281 // Font faces created with the JS API will not be reflected in the
13282 // stylesheets and need to be copied over to the cloned document.
13283 if (const FontFaceSet* set = GetFonts()) {
13284 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13287 clonedDoc->mReferrerInfo =
13288 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13289 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13290 CachePrintSelectionRanges(*this, *clonedDoc);
13292 // We're done with the clone, embed ourselves into the document viewer and
13293 // clone our children. The order here is pretty important, because our
13294 // document our document needs to have an owner global before we can create
13295 // the frame loaders for subdocuments.
13296 aViewer->SetDocument(clonedDoc);
13298 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13300 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13301 for (const auto& clone : pendingClones) {
13302 RefPtr<Element> element = do_QueryObject(clone.mElement);
13303 RefPtr<nsFrameLoader> frameLoader =
13304 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13306 if (NS_WARN_IF(!frameLoader)) {
13307 continue;
13310 clone.mElement->SetFrameLoader(frameLoader);
13312 nsresult rv = frameLoader->FinishStaticClone(
13313 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13314 Unused << NS_WARN_IF(NS_FAILED(rv));
13317 return clonedDoc.forget();
13320 void Document::UnlinkOriginalDocumentIfStatic() {
13321 if (IsStaticDocument() && mOriginalDocument) {
13322 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13323 mOriginalDocument->mStaticCloneCount--;
13324 mOriginalDocument = nullptr;
13326 MOZ_ASSERT(!mOriginalDocument);
13329 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13330 int32_t* aHandle) {
13331 nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
13332 if (NS_FAILED(rv)) {
13333 return rv;
13336 UpdateFrameRequestCallbackSchedulingState();
13337 return NS_OK;
13340 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13341 if (mFrameRequestManager.Cancel(aHandle)) {
13342 UpdateFrameRequestCallbackSchedulingState();
13346 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13347 return mFrameRequestManager.IsCanceled(aHandle);
13350 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
13351 // Get the document's current state object. This is the object backing both
13352 // history.state and popStateEvent.state.
13354 // mStateObjectContainer may be null; this just means that there's no
13355 // current state object.
13357 if (mStateObjectCached.isNothing()) {
13358 if (mStateObjectContainer) {
13359 AutoJSAPI jsapi;
13360 // Init with null is "OK" in the sense that it will just fail.
13361 if (!jsapi.Init(GetScopeObject())) {
13362 return NS_ERROR_UNEXPECTED;
13364 JS::Rooted<JS::Value> value(jsapi.cx());
13365 nsresult rv =
13366 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value);
13367 NS_ENSURE_SUCCESS(rv, rv);
13369 mStateObjectCached.emplace(value);
13370 if (!value.isNullOrUndefined()) {
13371 mozilla::HoldJSObjects(this);
13373 } else {
13374 mStateObjectCached.emplace(JS::NullValue());
13378 aState.set(mStateObjectCached.ref());
13380 return NS_OK;
13383 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13384 mTiming = aTiming;
13385 if (!mLoadingTimeStamp.IsNull() && mTiming) {
13386 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), mLoadingTimeStamp);
13389 // If there's already the DocumentTimeline instance, tell it since the
13390 // DocumentTimeline is based on both the navigation start time stamp and the
13391 // refresh driver timestamp.
13392 if (mDocumentTimeline) {
13393 mDocumentTimeline->UpdateLastRefreshDriverTime();
13397 nsContentList* Document::ImageMapList() {
13398 if (!mImageMaps) {
13399 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13400 nsGkAtoms::map);
13403 return mImageMaps;
13406 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13407 static const char* kDeprecationWarnings[] = {
13408 #include "nsDeprecatedOperationList.h"
13409 nullptr};
13410 #undef DEPRECATED_OPERATION
13412 #define DOCUMENT_WARNING(_op) #_op "Warning",
13413 static const char* kDocumentWarnings[] = {
13414 #include "nsDocumentWarningList.h"
13415 nullptr};
13416 #undef DOCUMENT_WARNING
13418 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13419 switch (aOperation) {
13420 #define DEPRECATED_OPERATION(_op) \
13421 case DeprecatedOperations::e##_op: \
13422 return eUseCounter_##_op;
13423 #include "nsDeprecatedOperationList.h"
13424 #undef DEPRECATED_OPERATION
13425 default:
13426 MOZ_CRASH();
13430 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13431 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13434 void Document::WarnOnceAbout(
13435 DeprecatedOperations aOperation, bool asError /* = false */,
13436 const nsTArray<nsString>& aParams /* = empty array */) const {
13437 MOZ_ASSERT(NS_IsMainThread());
13438 if (HasWarnedAbout(aOperation)) {
13439 return;
13441 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13442 // Don't count deprecated operations for about pages since those pages
13443 // are almost in our control, and we always need to remove uses there
13444 // before we remove the operation itself anyway.
13445 if (!IsAboutPage()) {
13446 const_cast<Document*>(this)->SetUseCounter(
13447 OperationToUseCounter(aOperation));
13449 uint32_t flags =
13450 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13451 nsContentUtils::ReportToConsole(
13452 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13453 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13456 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13457 return mDocWarningWarnedAbout[aWarning];
13460 void Document::WarnOnceAbout(
13461 DocumentWarnings aWarning, bool asError /* = false */,
13462 const nsTArray<nsString>& aParams /* = empty array */) const {
13463 MOZ_ASSERT(NS_IsMainThread());
13464 if (HasWarnedAbout(aWarning)) {
13465 return;
13467 mDocWarningWarnedAbout[aWarning] = true;
13468 uint32_t flags =
13469 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13470 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13471 nsContentUtils::eDOM_PROPERTIES,
13472 kDocumentWarnings[aWarning], aParams);
13475 mozilla::dom::ImageTracker* Document::ImageTracker() {
13476 if (!mImageTracker) {
13477 mImageTracker = new mozilla::dom::ImageTracker;
13479 return mImageTracker;
13482 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13483 SVGUseElement& aUseElement) {
13484 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13486 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13487 // Printing doesn't deal well with dynamic DOM mutations.
13488 return;
13491 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13493 if (PresShell* presShell = GetPresShell()) {
13494 presShell->EnsureStyleFlush();
13498 void Document::DoUpdateSVGUseElementShadowTrees() {
13499 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13501 do {
13502 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13503 mSVGUseElementsNeedingShadowTreeUpdate);
13504 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13506 for (const auto& useElement : useElementsToUpdate) {
13507 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13508 // The element was in another <use> shadow tree which we processed
13509 // already and also needed an update, and is removed from the document
13510 // now, so nothing to do here.
13511 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13512 continue;
13514 useElement->UpdateShadowTree();
13516 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13519 void Document::NotifyMediaFeatureValuesChanged() {
13520 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13521 imageElement->MediaFeatureValuesChanged();
13525 already_AddRefed<Touch> Document::CreateTouch(
13526 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13527 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13528 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13529 float aRotationAngle, float aForce) {
13530 RefPtr<Touch> touch =
13531 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13532 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13533 return touch.forget();
13536 already_AddRefed<TouchList> Document::CreateTouchList() {
13537 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13538 return retval.forget();
13541 already_AddRefed<TouchList> Document::CreateTouchList(
13542 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13543 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13544 retval->Append(&aTouch);
13545 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13546 retval->Append(aTouches[i].get());
13548 return retval.forget();
13551 already_AddRefed<TouchList> Document::CreateTouchList(
13552 const Sequence<OwningNonNull<Touch>>& aTouches) {
13553 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13554 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13555 retval->Append(aTouches[i].get());
13557 return retval.forget();
13560 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13561 float aX, float aY) {
13562 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13564 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13565 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13566 nsPoint pt(x, y);
13568 FlushPendingNotifications(FlushType::Layout);
13570 PresShell* presShell = GetPresShell();
13571 if (!presShell) {
13572 return nullptr;
13575 nsIFrame* rootFrame = presShell->GetRootFrame();
13577 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13578 if (!rootFrame) {
13579 return nullptr;
13582 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13583 RelativeTo{rootFrame}, pt,
13584 {{FrameForPointOption::IgnorePaintSuppression,
13585 FrameForPointOption::IgnoreCrossDoc}});
13586 if (!ptFrame) {
13587 return nullptr;
13590 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13591 nsPoint aOffset;
13592 nsCOMPtr<nsIWidget> widget = nsContentUtils::GetWidget(presShell, &aOffset);
13593 LayoutDeviceIntPoint refPoint = nsContentUtils::ToWidgetPoint(
13594 CSSPoint(aX, aY), aOffset, GetPresContext());
13595 nsPoint adjustedPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
13596 widget, refPoint, RelativeTo{ptFrame});
13598 nsIFrame::ContentOffsets offsets =
13599 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13601 nsCOMPtr<nsIContent> node = offsets.content;
13602 uint32_t offset = offsets.offset;
13603 nsCOMPtr<nsIContent> anonNode = node;
13604 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13605 if (nodeIsAnonymous) {
13606 node = ptFrame->GetContent();
13607 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13608 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13609 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13610 if (textFrame) {
13611 // If the anonymous content node has a child, then we need to make sure
13612 // that we get the appropriate child, as otherwise the offset may not be
13613 // correct when we construct a range for it.
13614 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13615 if (firstChild) {
13616 anonNode = firstChild;
13619 if (textArea) {
13620 offset =
13621 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13624 node = nonanon;
13625 } else {
13626 node = nullptr;
13627 offset = 0;
13631 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13632 if (nodeIsAnonymous) {
13633 aCaretPos->SetAnonymousContentNode(anonNode);
13635 return aCaretPos.forget();
13638 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13639 // We rely on correct frame information here, so need to flush frames.
13640 FlushPendingNotifications(FlushType::Frames);
13642 // An element that is the HTML body element is potentially scrollable if all
13643 // of the following conditions are true:
13645 // The element has an associated CSS layout box.
13646 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13647 if (!bodyFrame) {
13648 return false;
13651 // The element's parent element's computed value of the overflow-x and
13652 // overflow-y properties are visible.
13653 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13654 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13655 if (parentFrame &&
13656 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13657 return false;
13660 // The element's computed value of the overflow-x or overflow-y properties is
13661 // not visible.
13662 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13665 Element* Document::GetScrollingElement() {
13666 // Keep this in sync with IsScrollingElement.
13667 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13668 RefPtr<HTMLBodyElement> body = GetBodyElement();
13669 if (body && !IsPotentiallyScrollable(body)) {
13670 return body;
13673 return nullptr;
13676 return GetRootElement();
13679 bool Document::IsScrollingElement(Element* aElement) {
13680 // Keep this in sync with GetScrollingElement.
13681 MOZ_ASSERT(aElement);
13683 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13684 return aElement == GetRootElement();
13687 // In the common case when aElement != body, avoid refcounting.
13688 HTMLBodyElement* body = GetBodyElement();
13689 if (aElement != body) {
13690 return false;
13693 // Now we know body is non-null, since aElement is not null. It's the
13694 // scrolling element for the document if it itself is not potentially
13695 // scrollable.
13696 RefPtr<HTMLBodyElement> strongBody(body);
13697 return !IsPotentiallyScrollable(strongBody);
13700 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13701 public:
13702 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
13703 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13705 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13706 const BlockParsingOptions& aOptions)
13707 : mPromise(aPromise) {
13708 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13709 if (parser &&
13710 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13711 parser->BlockParser();
13712 mParser = do_GetWeakReference(parser);
13713 mDocument = aDocument;
13714 mDocument->BlockOnload();
13715 mDocument->BlockDOMContentLoaded();
13719 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13720 ErrorResult& aRv) override {
13721 MaybeUnblockParser();
13723 mPromise->MaybeResolve(aValue);
13726 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13727 ErrorResult& aRv) override {
13728 MaybeUnblockParser();
13730 mPromise->MaybeReject(aValue);
13733 protected:
13734 virtual ~UnblockParsingPromiseHandler() {
13735 // If we're being cleaned up by the cycle collector, our mDocument reference
13736 // may have been unlinked while our mParser weak reference is still alive.
13737 if (mDocument) {
13738 MaybeUnblockParser();
13742 private:
13743 void MaybeUnblockParser() {
13744 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13745 if (parser) {
13746 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13747 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13748 if (parser == docParser) {
13749 parser->UnblockParser();
13750 parser->ContinueInterruptedParsingAsync();
13753 if (mDocument) {
13754 // We blocked DOMContentLoaded and load events on this document. Unblock
13755 // them. Note that we want to do that no matter what's going on with the
13756 // parser state for this document. Maybe someone caused it to stop being
13757 // parsed, so CreatorParserOrNull() is returning null, but we still want
13758 // to unblock these.
13759 mDocument->UnblockDOMContentLoaded();
13760 mDocument->UnblockOnload(false);
13762 mParser = nullptr;
13763 mDocument = nullptr;
13766 nsWeakPtr mParser;
13767 RefPtr<Promise> mPromise;
13768 RefPtr<Document> mDocument;
13771 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
13773 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
13774 NS_INTERFACE_MAP_ENTRY(nsISupports)
13775 NS_INTERFACE_MAP_END
13777 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
13778 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
13780 already_AddRefed<Promise> Document::BlockParsing(
13781 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
13782 RefPtr<Promise> resultPromise =
13783 Promise::Create(aPromise.GetParentObject(), aRv);
13784 if (aRv.Failed()) {
13785 return nullptr;
13788 RefPtr<PromiseNativeHandler> promiseHandler =
13789 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
13790 aPromise.AppendNativeHandler(promiseHandler);
13792 return resultPromise.forget();
13795 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
13796 if (mFailedChannel) {
13797 nsCOMPtr<nsIURI> failedURI;
13798 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
13799 return failedURI.forget();
13803 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
13804 if (!uri) {
13805 return nullptr;
13808 return uri.forget();
13811 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
13812 if (mIsGoingAway) {
13813 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13814 return nullptr;
13817 if (!mReadyForIdle) {
13818 nsIGlobalObject* global = GetScopeObject();
13819 if (!global) {
13820 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13821 return nullptr;
13824 mReadyForIdle = Promise::Create(global, aRv);
13825 if (aRv.Failed()) {
13826 return nullptr;
13830 return mReadyForIdle;
13833 void Document::MaybeResolveReadyForIdle() {
13834 IgnoredErrorResult rv;
13835 Promise* readyPromise = GetDocumentReadyForIdle(rv);
13836 if (readyPromise) {
13837 readyPromise->MaybeResolveWithUndefined();
13841 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
13842 // The policy is created when the document is initialized. We _must_ have a
13843 // policy here even if the featurePolicy pref is off. If this assertion fails,
13844 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
13845 MOZ_ASSERT(mFeaturePolicy);
13846 return mFeaturePolicy;
13849 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
13850 // Only chrome documents are allowed to use command dispatcher.
13851 if (!nsContentUtils::IsChromeDoc(this)) {
13852 return nullptr;
13854 if (!mCommandDispatcher) {
13855 // Create our command dispatcher and hook it up.
13856 mCommandDispatcher = new nsXULCommandDispatcher(this);
13858 return mCommandDispatcher;
13861 void Document::InitializeXULBroadcastManager() {
13862 if (mXULBroadcastManager) {
13863 return;
13865 mXULBroadcastManager = new XULBroadcastManager(this);
13868 namespace {
13870 class DevToolsMutationObserver final : public nsStubMutationObserver {
13871 NS_DECL_ISUPPORTS
13872 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
13873 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
13874 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
13876 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
13877 // relies on the event firing _before_ the removal happens.
13878 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
13880 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
13881 // data changes right now (maybe intentionally?).
13882 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
13884 DevToolsMutationObserver() = default;
13886 private:
13887 void FireEvent(nsINode* aTarget, const nsAString& aType);
13889 ~DevToolsMutationObserver() = default;
13892 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
13894 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
13895 const nsAString& aType) {
13896 if (aTarget->ChromeOnlyAccess()) {
13897 return;
13899 (new AsyncEventDispatcher(aTarget, aType, CanBubble::eNo,
13900 ChromeOnlyDispatch::eYes, Composed::eYes))
13901 ->RunDOMEventWhenSafe();
13904 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
13905 int32_t aNamespaceID,
13906 nsAtom* aAttribute,
13907 int32_t aModType,
13908 const nsAttrValue* aOldValue) {
13909 FireEvent(aElement, u"devtoolsattrmodified"_ns);
13912 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
13913 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
13914 ContentInserted(c);
13918 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
13919 FireEvent(aChild, u"devtoolschildinserted"_ns);
13922 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
13924 } // namespace
13926 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
13927 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
13928 return;
13930 mDevToolsWatchingDOMMutations = aValue;
13931 if (aValue) {
13932 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
13933 sDevToolsMutationObserver = new DevToolsMutationObserver();
13934 ClearOnShutdown(&sDevToolsMutationObserver);
13936 AddMutationObserver(sDevToolsMutationObserver);
13937 } else if (sDevToolsMutationObserver) {
13938 RemoveMutationObserver(sDevToolsMutationObserver);
13942 void Document::MaybeWarnAboutZoom() {
13943 if (mHasWarnedAboutZoom) {
13944 return;
13946 const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
13947 mStyleUseCounters.get(), eCSSProperty_zoom);
13948 if (!usedZoom) {
13949 return;
13952 mHasWarnedAboutZoom = true;
13953 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
13954 this, nsContentUtils::eLAYOUT_PROPERTIES,
13955 "ZoomPropertyWarning");
13958 nsIHTMLCollection* Document::Children() {
13959 if (!mChildrenCollection) {
13960 mChildrenCollection =
13961 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
13962 nsGkAtoms::_asterisk, false);
13965 return mChildrenCollection;
13968 uint32_t Document::ChildElementCount() { return Children()->Length(); }
13970 // Singleton class to manage the list of fullscreen documents which are the
13971 // root of a branch which contains fullscreen documents. We maintain this list
13972 // so that we can easily exit all windows from fullscreen when the user
13973 // presses the escape key.
13974 class FullscreenRoots {
13975 public:
13976 // Adds the root of given document to the manager. Calling this method
13977 // with a document whose root is already contained has no effect.
13978 static void Add(Document* aDoc);
13980 // Iterates over every root in the root list, and calls aFunction, passing
13981 // each root once to aFunction. It is safe to call Add() and Remove() while
13982 // iterating over the list (i.e. in aFunction). Documents that are removed
13983 // from the manager during traversal are not traversed, and documents that
13984 // are added to the manager during traversal are also not traversed.
13985 static void ForEach(void (*aFunction)(Document* aDoc));
13987 // Removes the root of a specific document from the manager.
13988 static void Remove(Document* aDoc);
13990 // Returns true if all roots added to the list have been removed.
13991 static bool IsEmpty();
13993 private:
13994 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
13995 MOZ_COUNTED_DTOR(FullscreenRoots)
13997 enum : uint32_t { NotFound = uint32_t(-1) };
13998 // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
13999 static uint32_t Find(Document* aRoot);
14001 // Returns true if aRoot is in the list of fullscreen roots.
14002 static bool Contains(Document* aRoot);
14004 // Singleton instance of the FullscreenRoots. This is instantiated when a
14005 // root is added, and it is deleted when the last root is removed.
14006 static FullscreenRoots* sInstance;
14008 // List of weak pointers to roots.
14009 nsTArray<nsWeakPtr> mRoots;
14012 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
14014 /* static */
14015 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
14016 if (!sInstance) {
14017 return;
14019 // Create a copy of the roots array, and iterate over the copy. This is so
14020 // that if an element is removed from mRoots we don't mess up our iteration.
14021 nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
14022 // Call aFunction on all entries.
14023 for (uint32_t i = 0; i < roots.Length(); i++) {
14024 nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
14025 // Check that the root isn't in the manager. This is so that new additions
14026 // while we were running don't get traversed.
14027 if (root && FullscreenRoots::Contains(root)) {
14028 aFunction(root);
14033 /* static */
14034 bool FullscreenRoots::Contains(Document* aRoot) {
14035 return FullscreenRoots::Find(aRoot) != NotFound;
14038 /* static */
14039 void FullscreenRoots::Add(Document* aDoc) {
14040 nsCOMPtr<Document> root =
14041 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14042 if (!FullscreenRoots::Contains(root)) {
14043 if (!sInstance) {
14044 sInstance = new FullscreenRoots();
14046 sInstance->mRoots.AppendElement(do_GetWeakReference(root));
14050 /* static */
14051 uint32_t FullscreenRoots::Find(Document* aRoot) {
14052 if (!sInstance) {
14053 return NotFound;
14055 nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
14056 for (uint32_t i = 0; i < roots.Length(); i++) {
14057 nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
14058 if (otherRoot == aRoot) {
14059 return i;
14062 return NotFound;
14065 /* static */
14066 void FullscreenRoots::Remove(Document* aDoc) {
14067 nsCOMPtr<Document> root =
14068 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14069 uint32_t index = Find(root);
14070 NS_ASSERTION(index != NotFound,
14071 "Should only try to remove roots which are still added!");
14072 if (index == NotFound || !sInstance) {
14073 return;
14075 sInstance->mRoots.RemoveElementAt(index);
14076 if (sInstance->mRoots.IsEmpty()) {
14077 delete sInstance;
14078 sInstance = nullptr;
14082 /* static */
14083 bool FullscreenRoots::IsEmpty() { return !sInstance; }
14085 // Any fullscreen change waiting for the widget to finish transition
14086 // is queued here. This is declared static instead of a member of
14087 // Document because in the majority of time, there would be at most
14088 // one document requesting or exiting fullscreen. We shouldn't waste
14089 // the space to hold for it in every document.
14090 class PendingFullscreenChangeList {
14091 public:
14092 PendingFullscreenChangeList() = delete;
14094 template <typename T>
14095 static void Add(UniquePtr<T> aChange) {
14096 sList.insertBack(aChange.release());
14099 static const FullscreenChange* GetLast() { return sList.getLast(); }
14101 enum IteratorOption {
14102 // When we are committing fullscreen changes or preparing for
14103 // that, we generally want to iterate all requests in the same
14104 // window with eDocumentsWithSameRoot option.
14105 eDocumentsWithSameRoot,
14106 // If we are removing a document from the tree, we would only
14107 // want to remove the requests from the given document and its
14108 // descendants. For that case, use eInclusiveDescendants.
14109 eInclusiveDescendants
14112 template <typename T>
14113 class Iterator {
14114 public:
14115 explicit Iterator(Document* aDoc, IteratorOption aOption)
14116 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
14117 if (mCurrent) {
14118 if (aDoc->GetBrowsingContext()) {
14119 mRootBCForIteration = aDoc->GetBrowsingContext();
14120 if (aOption == eDocumentsWithSameRoot) {
14121 RefPtr<BrowsingContext> bc =
14122 GetParentIgnoreChromeBoundary(mRootBCForIteration);
14123 while (bc) {
14124 mRootBCForIteration = bc;
14125 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
14129 SkipToNextMatch();
14133 UniquePtr<T> TakeAndNext() {
14134 auto thisChange = TakeAndNextInternal();
14135 SkipToNextMatch();
14136 return thisChange;
14138 bool AtEnd() const { return mCurrent == nullptr; }
14140 private:
14141 already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
14142 BrowsingContext* aBC) {
14143 // Chrome BrowsingContexts are only available in the parent process, so if
14144 // we're in a content process, we only worry about the context tree.
14145 if (XRE_IsParentProcess()) {
14146 return aBC->Canonical()->GetParentCrossChromeBoundary();
14148 return do_AddRef(aBC->GetParent());
14151 UniquePtr<T> TakeAndNextInternal() {
14152 FullscreenChange* thisChange = mCurrent;
14153 MOZ_ASSERT(thisChange->Type() == T::kType);
14154 mCurrent = mCurrent->removeAndGetNext();
14155 return WrapUnique(static_cast<T*>(thisChange));
14157 void SkipToNextMatch() {
14158 while (mCurrent) {
14159 if (mCurrent->Type() == T::kType) {
14160 RefPtr<BrowsingContext> bc =
14161 mCurrent->Document()->GetBrowsingContext();
14162 if (!bc) {
14163 // Always automatically drop fullscreen changes which are
14164 // from a document detached from the doc shell.
14165 UniquePtr<T> change = TakeAndNextInternal();
14166 change->MayRejectPromise("Document is not active");
14167 continue;
14169 while (bc && bc != mRootBCForIteration) {
14170 bc = GetParentIgnoreChromeBoundary(bc);
14172 if (bc) {
14173 break;
14176 // The current one either don't have matched type, or isn't
14177 // inside the given subtree, so skip this item.
14178 mCurrent = mCurrent->getNext();
14182 FullscreenChange* mCurrent;
14183 RefPtr<BrowsingContext> mRootBCForIteration;
14186 private:
14187 static LinkedList<FullscreenChange> sList;
14190 /* static */
14191 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14193 Document* Document::GetFullscreenRoot() {
14194 nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
14195 return root;
14198 size_t Document::CountFullscreenElements() const {
14199 size_t count = 0;
14200 for (const nsWeakPtr& ptr : mTopLayer) {
14201 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14202 if (elem->State().HasState(ElementState::FULLSCREEN)) {
14203 count++;
14207 return count;
14210 void Document::SetFullscreenRoot(Document* aRoot) {
14211 mFullscreenRoot = do_GetWeakReference(aRoot);
14214 void Document::TryCancelDialog() {
14215 // Check if the document is blocked by modal dialog
14216 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14217 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14218 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14219 dialog->QueueCancelDialog();
14220 break;
14225 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14226 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14227 RefPtr<Promise> promise = exit->GetPromise();
14228 RestorePreviousFullscreenState(std::move(exit));
14229 return promise.forget();
14232 static void AskWindowToExitFullscreen(Document* aDoc) {
14233 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14234 nsContentUtils::DispatchEventOnlyToChrome(
14235 aDoc, ToSupports(aDoc), u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14236 Cancelable::eNo, /* DefaultAction */ nullptr);
14237 } else {
14238 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14239 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14244 class nsCallExitFullscreen : public Runnable {
14245 public:
14246 explicit nsCallExitFullscreen(Document* aDoc)
14247 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14249 NS_IMETHOD Run() final {
14250 if (!mDoc) {
14251 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14252 } else {
14253 AskWindowToExitFullscreen(mDoc);
14255 return NS_OK;
14258 private:
14259 nsCOMPtr<Document> mDoc;
14262 /* static */
14263 void Document::AsyncExitFullscreen(Document* aDoc) {
14264 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14265 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14266 if (aDoc) {
14267 aDoc->Dispatch(TaskCategory::Other, exit.forget());
14268 } else {
14269 NS_DispatchToCurrentThread(exit.forget());
14273 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14274 uint32_t count = 0;
14275 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14276 auto subDoc = [&count](Document& aSubDoc) {
14277 if (aSubDoc.Fullscreen()) {
14278 count++;
14280 return CallState::Continue;
14282 aDoc.EnumerateSubDocuments(subDoc);
14283 return count;
14286 bool Document::IsFullscreenLeaf() {
14287 // A fullscreen leaf document is fullscreen, and has no fullscreen
14288 // subdocuments.
14290 // FIXME(emilio): This doesn't seem to account for fission iframes, is that
14291 // ok?
14292 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0;
14295 static Document* GetFullscreenLeaf(Document& aDoc) {
14296 if (aDoc.IsFullscreenLeaf()) {
14297 return &aDoc;
14299 if (!aDoc.Fullscreen()) {
14300 return nullptr;
14302 Document* leaf = nullptr;
14303 auto recurse = [&leaf](Document& aSubDoc) {
14304 leaf = GetFullscreenLeaf(aSubDoc);
14305 return leaf ? CallState::Stop : CallState::Continue;
14307 aDoc.EnumerateSubDocuments(recurse);
14308 return leaf;
14311 static Document* GetFullscreenLeaf(Document* aDoc) {
14312 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14313 return leaf;
14315 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14316 // below the fullscreen doc. Start the search from the root.
14317 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14318 return GetFullscreenLeaf(*root);
14321 static CallState ResetFullscreen(Document& aDocument) {
14322 if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) {
14323 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
14324 "Should have at most 1 fullscreen subdocument.");
14325 aDocument.CleanupFullscreenState();
14326 NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen");
14327 DispatchFullscreenChange(aDocument, fsElement);
14328 aDocument.EnumerateSubDocuments(ResetFullscreen);
14330 return CallState::Continue;
14333 // Since Document::ExitFullscreenInDocTree() could be called from
14334 // Element::UnbindFromTree() where it is not safe to synchronously run
14335 // script. This runnable is the script part of that function.
14336 class ExitFullscreenScriptRunnable : public Runnable {
14337 public:
14338 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
14339 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
14340 mRoot(aRoot),
14341 mLeaf(aLeaf) {}
14343 NS_IMETHOD Run() override {
14344 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
14345 // document since we want this event to follow the same path that
14346 // MozDOMFullscreen:Entered was dispatched.
14347 nsContentUtils::DispatchEventOnlyToChrome(
14348 mLeaf, ToSupports(mLeaf), u"MozDOMFullscreen:Exited"_ns,
14349 CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
14350 // Ensure the window exits fullscreen.
14351 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
14352 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
14353 false);
14355 return NS_OK;
14358 private:
14359 nsCOMPtr<Document> mRoot;
14360 nsCOMPtr<Document> mLeaf;
14363 /* static */
14364 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
14365 MOZ_ASSERT(aMaybeNotARootDoc);
14367 // Unlock the pointer
14368 PointerLockManager::Unlock();
14370 // Resolve all promises which waiting for exit fullscreen.
14371 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
14372 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14373 while (!iter.AtEnd()) {
14374 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
14375 exit->MayResolvePromise();
14378 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
14379 if (!root || !root->Fullscreen()) {
14380 // If a document was detached before exiting from fullscreen, it is
14381 // possible that the root had left fullscreen state. In this case,
14382 // we would not get anything from the ResetFullscreen() call. Root's
14383 // not being a fullscreen doc also means the widget should have
14384 // exited fullscreen state. It means even if we do not return here,
14385 // we would actually do nothing below except crashing ourselves via
14386 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
14387 // document.
14388 return;
14391 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
14392 // See ExitFullscreenScriptRunnable::Run for details. We have to
14393 // record it here because we don't have such information after we
14394 // reset the fullscreen state below.
14395 Document* fullscreenLeaf = GetFullscreenLeaf(root);
14397 // Walk the tree of fullscreen documents, and reset their fullscreen state.
14398 ResetFullscreen(*root);
14400 NS_ASSERTION(!root->Fullscreen(),
14401 "Fullscreen root should no longer be a fullscreen doc...");
14403 // Move the top-level window out of fullscreen mode.
14404 FullscreenRoots::Remove(root);
14406 nsContentUtils::AddScriptRunner(
14407 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
14410 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
14411 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14412 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
14413 CanBubble::eYes, ChromeOnlyDispatch::eYes);
14414 asyncDispatcher->PostDOMEvent();
14417 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
14418 NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(),
14419 "Should have at least 1 fullscreen root when fullscreen!");
14421 if (!GetWindow()) {
14422 aExit->MayRejectPromise("No active window");
14423 return;
14425 if (!Fullscreen() || FullscreenRoots::IsEmpty()) {
14426 aExit->MayRejectPromise("Not in fullscreen mode");
14427 return;
14430 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
14431 AutoTArray<Element*, 8> exitElements;
14433 Document* doc = fullScreenDoc;
14434 // Collect all subdocuments.
14435 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
14436 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14437 MOZ_ASSERT(fsElement,
14438 "Parent document of "
14439 "a fullscreen document without fullscreen element?");
14440 exitElements.AppendElement(fsElement);
14442 MOZ_ASSERT(doc == this, "Must have reached this doc");
14443 // Collect all ancestor documents which we are going to change.
14444 for (; doc; doc = doc->GetInProcessParentDocument()) {
14445 Element* fsElement = doc->GetUnretargetedFullscreenElement();
14446 MOZ_ASSERT(fsElement,
14447 "Ancestor of fullscreen document must also be in fullscreen");
14448 if (doc != this) {
14449 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
14450 if (iframe->FullscreenFlag()) {
14451 // If this is an iframe, and it explicitly requested
14452 // fullscreen, don't rollback it automatically.
14453 break;
14457 exitElements.AppendElement(fsElement);
14458 if (doc->CountFullscreenElements() > 1) {
14459 break;
14463 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
14464 size_t fullscreenCount = lastDoc->CountFullscreenElements();
14465 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
14466 // If we are fully exiting fullscreen, don't touch anything here,
14467 // just wait for the window to get out from fullscreen first.
14468 PendingFullscreenChangeList::Add(std::move(aExit));
14469 AskWindowToExitFullscreen(this);
14470 return;
14473 // If fullscreen mode is updated the pointer should be unlocked
14474 PointerLockManager::Unlock();
14475 // All documents listed in the array except the last one are going to
14476 // completely exit from the fullscreen state.
14477 for (auto i : IntegerRange(exitElements.Length() - 1)) {
14478 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
14480 // The last document will either rollback one fullscreen element, or
14481 // completely exit from the fullscreen state as well.
14482 Document* newFullscreenDoc;
14483 if (fullscreenCount > 1) {
14484 DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement();
14485 MOZ_ASSERT(removedFullscreenElement);
14486 newFullscreenDoc = lastDoc;
14487 } else {
14488 lastDoc->CleanupFullscreenState();
14489 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
14491 // Dispatch the fullscreenchange event to all document listed. Note
14492 // that the loop order is reversed so that events are dispatched in
14493 // the tree order as indicated in the spec.
14494 for (Element* e : Reversed(exitElements)) {
14495 DispatchFullscreenChange(*e->OwnerDoc(), e);
14497 aExit->MayResolvePromise();
14499 MOZ_ASSERT(newFullscreenDoc,
14500 "If we were going to exit from fullscreen on "
14501 "all documents in this doctree, we should've asked the window to "
14502 "exit first instead of reaching here.");
14503 if (fullScreenDoc != newFullscreenDoc &&
14504 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
14505 // We've popped so enough off the stack that we've rolled back to
14506 // a fullscreen element in a parent document. If this document is
14507 // cross origin, dispatch an event to chrome so it knows to show
14508 // the warning UI.
14509 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
14513 class nsCallRequestFullscreen : public Runnable {
14514 public:
14515 explicit nsCallRequestFullscreen(UniquePtr<FullscreenRequest> aRequest)
14516 : mozilla::Runnable("nsCallRequestFullscreen"),
14517 mRequest(std::move(aRequest)) {}
14519 NS_IMETHOD Run() override {
14520 Document* doc = mRequest->Document();
14521 doc->RequestFullscreen(std::move(mRequest));
14522 return NS_OK;
14525 UniquePtr<FullscreenRequest> mRequest;
14528 void Document::AsyncRequestFullscreen(UniquePtr<FullscreenRequest> aRequest) {
14529 // Request fullscreen asynchronously.
14530 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14531 nsCOMPtr<nsIRunnable> event =
14532 new nsCallRequestFullscreen(std::move(aRequest));
14533 Dispatch(TaskCategory::Other, event.forget());
14536 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
14537 if (nsPresContext* presContext = aDoc->GetPresContext()) {
14538 presContext->UpdateViewportScrollStylesOverride();
14542 static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
14543 // When a media element enters the fullscreen, we would like to notify that
14544 // to the media controller in order to update its status.
14545 if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
14546 mediaElem->NotifyFullScreenChanged();
14550 void Document::CleanupFullscreenState() {
14551 while (PopFullscreenElement(UpdateViewport::No)) {
14552 // Remove the next one if appropriate
14555 UpdateViewportScrollbarOverrideForFullscreen(this);
14556 mFullscreenRoot = nullptr;
14558 // Restore the zoom level that was in place prior to entering fullscreen.
14559 if (PresShell* presShell = GetPresShell()) {
14560 if (presShell->GetMobileViewportManager()) {
14561 presShell->SetResolutionAndScaleTo(
14562 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
14567 bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) {
14568 Element* removedElement = TopLayerPop([](Element* element) -> bool {
14569 return element->State().HasState(ElementState::FULLSCREEN);
14572 if (!removedElement) {
14573 return false;
14576 MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN));
14577 removedElement->RemoveStates(ElementState::FULLSCREEN);
14578 NotifyFullScreenChangedForMediaElement(*removedElement);
14579 // Reset iframe fullscreen flag.
14580 if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) {
14581 iframe->SetFullscreenFlag(false);
14583 if (aUpdateViewport == UpdateViewport::Yes) {
14584 UpdateViewportScrollbarOverrideForFullscreen(this);
14586 return true;
14589 void Document::SetFullscreenElement(Element& aElement) {
14590 aElement.AddStates(ElementState::FULLSCREEN);
14591 TopLayerPush(aElement);
14592 NotifyFullScreenChangedForMediaElement(aElement);
14593 UpdateViewportScrollbarOverrideForFullscreen(this);
14596 static ElementState TopLayerModalStates() {
14597 ElementState modalStates = ElementState::MODAL_DIALOG;
14598 if (StaticPrefs::dom_fullscreen_modal()) {
14599 modalStates |= ElementState::FULLSCREEN;
14601 return modalStates;
14604 void Document::TopLayerPush(Element& aElement) {
14605 const bool modal =
14606 aElement.State().HasAtLeastOneOfStates(TopLayerModalStates());
14608 auto predictFunc = [&aElement](Element* element) {
14609 return element == &aElement;
14611 TopLayerPop(predictFunc);
14613 mTopLayer.AppendElement(do_GetWeakReference(&aElement));
14614 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
14616 if (modal) {
14617 aElement.AddStates(ElementState::TOPMOST_MODAL);
14619 bool foundExistingModalElement = false;
14620 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14621 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14622 if (element && element != &aElement &&
14623 element->State().HasState(ElementState::TOPMOST_MODAL)) {
14624 element->RemoveStates(ElementState::TOPMOST_MODAL);
14625 foundExistingModalElement = true;
14626 break;
14630 if (!foundExistingModalElement) {
14631 Element* root = GetRootElement();
14632 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?");
14633 if (&aElement != root) {
14634 // Add inert to the root element so that the inertness is applied to the
14635 // entire document.
14636 root->AddStates(ElementState::INERT);
14642 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
14643 aDialogElement.AddStates(ElementState::MODAL_DIALOG);
14644 TopLayerPush(aDialogElement);
14647 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
14648 auto predicate = [&aDialogElement](Element* element) -> bool {
14649 return element == &aDialogElement;
14651 DebugOnly<Element*> removedElement = TopLayerPop(predicate);
14652 MOZ_ASSERT(removedElement == &aDialogElement);
14653 aDialogElement.RemoveStates(ElementState::MODAL_DIALOG);
14656 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
14657 if (mTopLayer.IsEmpty()) {
14658 return nullptr;
14661 // Remove the topmost element that qualifies aPredicate; This
14662 // is required is because the top layer contains not only
14663 // fullscreen elements, but also dialog elements.
14664 Element* removedElement = nullptr;
14665 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14666 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14667 if (element && aPredicate(element)) {
14668 removedElement = element;
14669 mTopLayer.RemoveElementAt(i);
14670 break;
14674 // Pop from the stack null elements (references to elements which have
14675 // been GC'd since they were added to the stack) and elements which are
14676 // no longer in this document.
14678 // FIXME(emilio): If this loop does something, it'd violate the assertions
14679 // from GetTopLayerTop()... What gives?
14680 while (!mTopLayer.IsEmpty()) {
14681 Element* element = GetTopLayerTop();
14682 if (!element || element->GetComposedDoc() != this) {
14683 mTopLayer.RemoveLastElement();
14684 } else {
14685 // The top element of the stack is now an in-doc element. Return here.
14686 break;
14690 if (!removedElement) {
14691 return nullptr;
14694 const ElementState modalStates = TopLayerModalStates();
14695 const bool modal = removedElement->State().HasAtLeastOneOfStates(modalStates);
14697 if (modal) {
14698 removedElement->RemoveStates(ElementState::TOPMOST_MODAL);
14699 bool foundExistingModalElement = false;
14700 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14701 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14702 if (element && element->State().HasAtLeastOneOfStates(modalStates)) {
14703 element->AddStates(ElementState::TOPMOST_MODAL);
14704 foundExistingModalElement = true;
14705 break;
14708 // No more modal elements, make the document not inert anymore.
14709 if (!foundExistingModalElement) {
14710 Element* root = GetRootElement();
14711 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14712 root->RemoveStates(ElementState::INERT);
14717 return removedElement;
14720 void Document::GetWireframe(bool aIncludeNodes,
14721 Nullable<Wireframe>& aWireframe) {
14722 FlushPendingNotifications(FlushType::Layout);
14723 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
14726 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
14727 Nullable<Wireframe>& aWireframe) {
14728 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
14729 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14731 PresShell* shell = GetPresShell();
14732 if (!shell) {
14733 return;
14736 nsPresContext* pc = shell->GetPresContext();
14737 if (!pc) {
14738 return;
14741 nsIFrame* rootFrame = shell->GetRootFrame();
14742 if (!rootFrame) {
14743 return;
14746 auto& wireframe = aWireframe.SetValue();
14747 wireframe.mCanvasBackground = shell->ComputeCanvasBackground().mColor;
14749 FrameForPointOptions options;
14750 options.mBits += FrameForPointOption::IgnoreCrossDoc;
14751 options.mBits += FrameForPointOption::IgnorePaintSuppression;
14752 options.mBits += FrameForPointOption::OnlyVisible;
14754 AutoTArray<nsIFrame*, 32> frames;
14755 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
14756 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
14757 options);
14759 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
14760 // something perhaps, but seems hard / like it'd involve at least some extra
14761 // copying around, since they don't outlive GetFramesForArea.
14762 auto& rects = wireframe.mRects.Construct();
14763 if (!rects.SetCapacity(frames.Length(), fallible)) {
14764 return;
14766 for (nsIFrame* frame : Reversed(frames)) {
14767 auto [rectColor,
14768 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> {
14769 if (frame->IsTextFrame()) {
14770 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
14771 WireframeRectType::Text};
14773 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
14774 return {0, WireframeRectType::Image};
14776 if (frame->IsThemed()) {
14777 return {0, WireframeRectType::Background};
14779 bool drawImage = false;
14780 bool drawColor = false;
14781 ComputedStyle* bgStyle = nullptr;
14782 if (nsCSSRendering::FindBackground(frame, &bgStyle)) {
14783 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
14784 pc, bgStyle, frame, drawImage, drawColor);
14785 if (drawImage &&
14786 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
14787 return {color, WireframeRectType::Image};
14789 if (drawColor && !frame->IsCanvasFrame()) {
14790 // Canvas frame background already accounted for in mCanvasBackground.
14791 return {color, WireframeRectType::Background};
14794 return {0, WireframeRectType::Unknown};
14795 }();
14797 if (rectType == WireframeRectType::Unknown) {
14798 continue;
14801 const auto r =
14802 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
14803 frame, frame->GetRectRelativeToSelf(), relativeTo));
14804 if ((uint32_t)r.Area() <
14805 StaticPrefs::browser_history_wireframeAreaThreshold()) {
14806 continue;
14809 // Can't really fail because SetCapacity succeeded.
14810 auto& taggedRect = *rects.AppendElement(fallible);
14812 if (aIncludeNodes) {
14813 if (nsIContent* c = frame->GetContent()) {
14814 taggedRect.mNode.Construct(c);
14817 taggedRect.mX = r.x;
14818 taggedRect.mY = r.y;
14819 taggedRect.mWidth = r.width;
14820 taggedRect.mHeight = r.height;
14821 taggedRect.mColor = rectColor;
14822 taggedRect.mType.Construct(rectType);
14826 Element* Document::GetTopLayerTop() {
14827 if (mTopLayer.IsEmpty()) {
14828 return nullptr;
14830 uint32_t last = mTopLayer.Length() - 1;
14831 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
14832 NS_ASSERTION(element, "Should have a top layer element!");
14833 NS_ASSERTION(element->IsInComposedDoc(),
14834 "Top layer element should be in doc");
14835 NS_ASSERTION(element->OwnerDoc() == this,
14836 "Top layer element should be in this doc");
14837 return element;
14840 Element* Document::GetUnretargetedFullscreenElement() const {
14841 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14842 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14843 // Per spec, the fullscreen element is the topmost element in the document’s
14844 // top layer whose fullscreen flag is set, if any, and null otherwise.
14845 if (element && element->State().HasState(ElementState::FULLSCREEN)) {
14846 return element;
14849 return nullptr;
14852 nsTArray<Element*> Document::GetTopLayer() const {
14853 nsTArray<Element*> elements;
14854 for (const nsWeakPtr& ptr : mTopLayer) {
14855 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14856 elements.AppendElement(elem);
14859 return elements;
14862 // Returns true if aDoc is in the focused tab in the active window.
14863 bool IsInActiveTab(Document* aDoc) {
14864 BrowsingContext* bc = aDoc->GetBrowsingContext();
14865 if (!bc) {
14866 return false;
14869 if (!bc->IsActive()) {
14870 return false;
14873 nsFocusManager* fm = nsFocusManager::GetFocusManager();
14874 if (!fm) {
14875 return false;
14878 if (XRE_IsParentProcess()) {
14879 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
14880 // by retaining the old code path for the parent process.
14881 nsIDocShell* docshell = aDoc->GetDocShell();
14882 if (!docshell) {
14883 return false;
14885 nsCOMPtr<nsIDocShellTreeItem> rootItem;
14886 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
14887 if (!rootItem) {
14888 return false;
14890 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
14891 if (!rootWin) {
14892 return false;
14895 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
14896 activeWindow = fm->GetActiveWindow();
14897 if (!activeWindow) {
14898 return false;
14901 return activeWindow == rootWin;
14904 return fm->GetActiveBrowsingContext() == bc->Top();
14907 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
14908 // Ensure the frame element is the fullscreen element in this document.
14909 // If the frame element is already the fullscreen element in this document,
14910 // this has no effect.
14911 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
14912 RequestFullscreen(std::move(request), XRE_IsContentProcess());
14915 void Document::RemoteFrameFullscreenReverted() {
14916 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
14917 RestorePreviousFullscreenState(std::move(exit));
14920 static bool HasFullscreenSubDocument(Document& aDoc) {
14921 uint32_t count = CountFullscreenSubDocuments(aDoc);
14922 NS_ASSERTION(count <= 1,
14923 "Fullscreen docs should have at most 1 fullscreen child!");
14924 return count >= 1;
14927 // Returns nullptr if a request for Fullscreen API is currently enabled
14928 // in the given document. Returns a static string indicates the reason
14929 // why it is not enabled otherwise.
14930 const char* Document::GetFullscreenError(CallerType aCallerType) {
14931 if (!StaticPrefs::full_screen_api_enabled()) {
14932 return "FullscreenDeniedDisabled";
14935 if (aCallerType == CallerType::System) {
14936 // Chrome code can always use the fullscreen API, provided it's not
14937 // explicitly disabled.
14938 return nullptr;
14941 if (!IsVisible()) {
14942 return "FullscreenDeniedHidden";
14945 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
14946 return "FullscreenDeniedFeaturePolicy";
14949 // Ensure that all containing elements are <iframe> and have allowfullscreen
14950 // attribute set.
14951 BrowsingContext* bc = GetBrowsingContext();
14952 if (!bc || !bc->FullscreenAllowed()) {
14953 return "FullscreenDeniedContainerNotAllowed";
14956 return nullptr;
14959 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
14960 Element* elem = aRequest.Element();
14961 // Strictly speaking, this isn't part of the fullscreen element ready
14962 // check in the spec, but per steps in the spec, when an element which
14963 // is already the fullscreen element requests fullscreen, nothing
14964 // should change and no event should be dispatched, but we still need
14965 // to resolve the returned promise.
14966 Element* fullscreenElement = GetUnretargetedFullscreenElement();
14967 if (elem == fullscreenElement) {
14968 aRequest.MayResolvePromise();
14969 return false;
14971 if (!elem->IsInComposedDoc()) {
14972 aRequest.Reject("FullscreenDeniedNotInDocument");
14973 return false;
14975 if (elem->OwnerDoc() != this) {
14976 aRequest.Reject("FullscreenDeniedMovedDocument");
14977 return false;
14979 if (!GetWindow()) {
14980 aRequest.Reject("FullscreenDeniedLostWindow");
14981 return false;
14983 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
14984 aRequest.Reject(msg);
14985 return false;
14987 if (HasFullscreenSubDocument(*this)) {
14988 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
14989 return false;
14991 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
14992 aRequest.Reject("FullscreenDeniedHTMLDialog");
14993 return false;
14995 // XXXsmaug Note, we don't follow the latest fullscreen spec here.
14996 // This whole check could be probably removed.
14997 if (fullscreenElement && !nsContentUtils::ContentIsHostIncludingDescendantOf(
14998 elem, fullscreenElement)) {
14999 // If this document is fullscreen, only grant fullscreen requests from
15000 // a descendant of the current fullscreen element.
15001 aRequest.Reject("FullscreenDeniedNotDescendant");
15002 return false;
15004 if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) {
15005 aRequest.Reject("FullscreenDeniedNotFocusedTab");
15006 return false;
15008 // Deny requests when a windowed plugin is focused.
15009 nsFocusManager* fm = nsFocusManager::GetFocusManager();
15010 if (!fm) {
15011 NS_WARNING("Failed to retrieve focus manager in fullscreen request.");
15012 aRequest.MayRejectPromise("An unexpected error occurred");
15013 return false;
15015 if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(
15016 fm->GetFocusedElement())) {
15017 aRequest.Reject("FullscreenDeniedFocusedPlugin");
15018 return false;
15020 return true;
15023 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
15024 MOZ_ASSERT(XRE_IsParentProcess());
15025 nsIDocShell* docShell = aDoc->GetDocShell();
15026 if (!docShell) {
15027 return nullptr;
15029 nsCOMPtr<nsIDocShellTreeItem> rootItem;
15030 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
15031 return rootItem ? rootItem->GetWindow() : nullptr;
15034 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
15035 nsPIDOMWindowOuter* aRootWin) {
15036 MOZ_ASSERT(XRE_IsParentProcess());
15037 // If we are in the chrome process, and the window has not been in
15038 // fullscreen, we certainly need to make that fullscreen first.
15039 if (!aRootWin->GetFullScreen()) {
15040 return false;
15042 // The iterator not being at end indicates there is still some
15043 // pending fullscreen request relates to this document. We have to
15044 // push the request to the pending queue so requests are handled
15045 // in the correct order.
15046 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15047 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15048 if (!iter.AtEnd()) {
15049 return false;
15051 // We have to apply the fullscreen state directly in this case,
15052 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
15053 // if it is already in fullscreen. If we do not apply the state but
15054 // instead add it to the queue and wait for the window as normal,
15055 // we would get stuck.
15056 return true;
15059 static bool CheckFullscreenAllowedElementType(const Element* elem) {
15060 // Per spec only HTML, <svg>, and <math> should be allowed, but
15061 // we also need to allow XUL elements right now.
15062 return elem->IsHTMLElement() || elem->IsXULElement() ||
15063 elem->IsSVGElement(nsGkAtoms::svg) ||
15064 elem->IsMathMLElement(nsGkAtoms::math);
15067 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
15068 bool aApplyFullscreenDirectly) {
15069 if (XRE_IsContentProcess()) {
15070 RequestFullscreenInContentProcess(std::move(aRequest),
15071 aApplyFullscreenDirectly);
15072 } else {
15073 RequestFullscreenInParentProcess(std::move(aRequest),
15074 aApplyFullscreenDirectly);
15078 void Document::RequestFullscreenInContentProcess(
15079 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15080 MOZ_ASSERT(XRE_IsContentProcess());
15082 // If we are in the content process, we can apply the fullscreen
15083 // state directly only if we have been in DOM fullscreen, because
15084 // otherwise we always need to notify the chrome.
15085 if (aApplyFullscreenDirectly ||
15086 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) {
15087 ApplyFullscreen(std::move(aRequest));
15088 return;
15091 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15092 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15093 return;
15096 // We don't need to check element ready before this point, because
15097 // if we called ApplyFullscreen, it would check that for us.
15098 if (!FullscreenElementReadyCheck(*aRequest)) {
15099 return;
15102 PendingFullscreenChangeList::Add(std::move(aRequest));
15103 // If we are not the top level process, dispatch an event to make
15104 // our parent process go fullscreen first.
15105 nsContentUtils::DispatchEventOnlyToChrome(
15106 this, ToSupports(this), u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
15107 Cancelable::eNo, /* DefaultAction */ nullptr);
15110 void Document::RequestFullscreenInParentProcess(
15111 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) {
15112 MOZ_ASSERT(XRE_IsParentProcess());
15113 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
15114 if (!rootWin) {
15115 aRequest->MayRejectPromise("No active window");
15116 return;
15119 if (aApplyFullscreenDirectly ||
15120 ShouldApplyFullscreenDirectly(this, rootWin)) {
15121 ApplyFullscreen(std::move(aRequest));
15122 return;
15125 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
15126 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
15127 return;
15130 // We don't need to check element ready before this point, because
15131 // if we called ApplyFullscreen, it would check that for us.
15132 if (!FullscreenElementReadyCheck(*aRequest)) {
15133 return;
15136 PendingFullscreenChangeList::Add(std::move(aRequest));
15137 // Make the window fullscreen.
15138 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
15141 /* static */
15142 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
15143 bool handled = false;
15144 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15145 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
15146 while (!iter.AtEnd()) {
15147 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15148 Document* doc = request->Document();
15149 if (doc->ApplyFullscreen(std::move(request))) {
15150 handled = true;
15153 return handled;
15156 static void ClearPendingFullscreenRequests(Document* aDoc) {
15157 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
15158 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
15159 while (!iter.AtEnd()) {
15160 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
15161 request->MayRejectPromise("Fullscreen request aborted");
15165 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
15166 if (!FullscreenElementReadyCheck(*aRequest)) {
15167 return false;
15170 // Stash a reference to any existing fullscreen doc, we'll use this later
15171 // to detect if the origin which is fullscreen has changed.
15172 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
15174 // Stores a list of documents which we must dispatch "fullscreenchange"
15175 // too. We're required by the spec to dispatch the events in root-to-leaf
15176 // order, but we traverse the doctree in a leaf-to-root order, so we save
15177 // references to the documents we must dispatch to so that we get the order
15178 // as specified.
15179 AutoTArray<Document*, 8> changed;
15181 // Remember the root document, so that if a fullscreen document is hidden
15182 // we can reset fullscreen state in the remaining visible fullscreen
15183 // documents.
15184 Document* fullScreenRootDoc =
15185 nsContentUtils::GetInProcessSubtreeRootDocument(this);
15187 // If a document is already in fullscreen, then unlock the mouse pointer
15188 // before setting a new document to fullscreen
15189 PointerLockManager::Unlock();
15191 // Set the fullscreen element. This sets the fullscreen style on the
15192 // element, and the fullscreen-ancestor styles on ancestors of the element
15193 // in this document.
15194 Element* elem = aRequest->Element();
15195 SetFullscreenElement(*elem);
15196 // Set the iframe fullscreen flag.
15197 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
15198 iframe->SetFullscreenFlag(true);
15200 changed.AppendElement(this);
15202 // Propagate up the document hierarchy, setting the fullscreen element as
15203 // the element's container in ancestor documents. This also sets the
15204 // appropriate css styles as well. Note we don't propagate down the
15205 // document hierarchy, the fullscreen element (or its container) is not
15206 // visible there. Stop when we reach the root document.
15207 Document* child = this;
15208 while (true) {
15209 child->SetFullscreenRoot(fullScreenRootDoc);
15211 // When entering fullscreen, reset the RCD's resolution to the intrinsic
15212 // resolution, otherwise the fullscreen content could be sized larger than
15213 // the screen (since fullscreen is implemented using position:fixed and
15214 // fixed elements are sized to the layout viewport).
15215 // This also ensures that things like video controls aren't zoomed in
15216 // when in fullscreen mode.
15217 if (PresShell* presShell = child->GetPresShell()) {
15218 if (RefPtr<MobileViewportManager> manager =
15219 presShell->GetMobileViewportManager()) {
15220 // Save the previous resolution so it can be restored.
15221 child->mSavedResolution = presShell->GetResolution();
15222 presShell->SetResolutionAndScaleTo(
15223 manager->ComputeIntrinsicResolution(),
15224 ResolutionChangeOrigin::MainThreadRestore);
15228 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
15229 "Fullscreen root should be set!");
15230 if (child == fullScreenRootDoc) {
15231 break;
15234 Element* element = child->GetEmbedderElement();
15235 if (!element) {
15236 // We've reached the root.No more changes need to be made
15237 // to the top layer stacks of documents further up the tree.
15238 break;
15241 Document* parent = child->GetInProcessParentDocument();
15242 parent->SetFullscreenElement(*element);
15243 changed.AppendElement(parent);
15244 child = parent;
15247 FullscreenRoots::Add(this);
15249 // If it is the first entry of the fullscreen, trigger an event so
15250 // that the UI can response to this change, e.g. hide chrome, or
15251 // notifying parent process to enter fullscreen. Note that chrome
15252 // code may also want to listen to MozDOMFullscreen:NewOrigin event
15253 // to pop up warning UI.
15254 if (!previousFullscreenDoc) {
15255 nsContentUtils::DispatchEventOnlyToChrome(
15256 this, ToSupports(elem), u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
15257 Cancelable::eNo, /* DefaultAction */ nullptr);
15260 // The origin which is fullscreen gets changed. Trigger an event so
15261 // that the chrome knows to pop up a warning UI. Note that
15262 // previousFullscreenDoc == nullptr upon first entry, so we always
15263 // take this path on the first entry. Also note that, in a multi-
15264 // process browser, the code in content process is responsible for
15265 // sending message with the origin to its parent, and the parent
15266 // shouldn't rely on this event itself.
15267 if (aRequest->mShouldNotifyNewOrigin &&
15268 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
15269 DispatchFullscreenNewOriginEvent(this);
15272 // Dispatch "fullscreenchange" events. Note that the loop order is
15273 // reversed so that events are dispatched in the tree order as
15274 // indicated in the spec.
15275 for (Document* d : Reversed(changed)) {
15276 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement());
15278 aRequest->MayResolvePromise();
15279 return true;
15282 void Document::ClearOrientationPendingPromise() {
15283 mOrientationPendingPromise = nullptr;
15286 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
15287 if (mIsGoingAway) {
15288 return false;
15291 mOrientationPendingPromise = aPromise;
15292 return true;
15295 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
15296 dom::VisibilityState oldState = mVisibilityState;
15297 mVisibilityState = ComputeVisibilityState();
15298 if (oldState != mVisibilityState) {
15299 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
15300 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
15301 u"visibilitychange"_ns,
15302 CanBubble::eYes, Cancelable::eNo);
15304 NotifyActivityChanged();
15305 if (mVisibilityState == dom::VisibilityState::Visible) {
15306 MaybeActiveMediaComponents();
15309 bool visible = !Hidden();
15310 for (auto* listener : mWorkerListeners) {
15311 listener->OnVisible(visible);
15316 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
15317 mWorkerListeners.Insert(aListener);
15318 aListener->OnVisible(!Hidden());
15321 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
15322 mWorkerListeners.Remove(aListener);
15325 VisibilityState Document::ComputeVisibilityState() const {
15326 // We have to check a few pieces of information here:
15327 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
15328 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
15329 // want to use GetWindow here because it does weird groveling for windows
15330 // in some cases.
15331 // 3) Is our outer window background? If so, we're hidden.
15332 // Otherwise, we're visible.
15333 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
15334 mWindow->GetOuterWindow()->IsBackground()) {
15335 return dom::VisibilityState::Hidden;
15338 return dom::VisibilityState::Visible;
15341 void Document::PostVisibilityUpdateEvent() {
15342 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
15343 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
15344 DispatchVisibilityChange::Yes);
15345 Dispatch(TaskCategory::Other, event.forget());
15348 void Document::MaybeActiveMediaComponents() {
15349 auto* window = GetWindow();
15350 if (!window || !window->ShouldDelayMediaFromStart()) {
15351 return;
15353 window->ActivateMediaComponents();
15356 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
15357 nsINode::AddSizeOfExcludingThis(aWindowSizes,
15358 &aWindowSizes.mDOMSizes.mDOMOtherSize);
15360 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
15361 AddSizeOfNodeTree(*kid, aWindowSizes);
15364 // IMPORTANT: for our ComputedValues measurements, we want to measure
15365 // ComputedValues accessible from DOM elements before ComputedValues not
15366 // accessible from DOM elements (i.e. accessible only from the frame tree).
15368 // Therefore, the measurement of the Document superclass must happen after
15369 // the measurement of DOM nodes (above), because Document contains the
15370 // PresShell, which contains the frame tree.
15371 if (mPresShell) {
15372 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
15375 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
15377 aWindowSizes.mPropertyTablesSize +=
15378 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15380 if (EventListenerManager* elm = GetExistingListenerManager()) {
15381 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15384 if (mNodeInfoManager) {
15385 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
15388 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15389 mDOMMediaQueryLists.sizeOfExcludingThis(
15390 aWindowSizes.mState.mMallocSizeOf);
15392 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
15393 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15394 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15397 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
15399 for (auto& sheetArray : mAdditionalSheets) {
15400 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15402 // Lumping in the loader with the style-sheets size is not ideal,
15403 // but most of the things in there are in fact stylesheets, so it
15404 // doesn't seem worthwhile to separate it out.
15405 // This can be null if we've already been unlinked.
15406 if (mCSSLoader) {
15407 aWindowSizes.mLayoutStyleSheetsSize +=
15408 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15411 if (mResizeObserverController) {
15412 mResizeObserverController->AddSizeOfIncludingThis(aWindowSizes);
15415 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15416 mAttrStyleSheet ? mAttrStyleSheet->DOMSizeOfIncludingThis(
15417 aWindowSizes.mState.mMallocSizeOf)
15418 : 0;
15420 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15421 mStyledLinks.ShallowSizeOfExcludingThis(
15422 aWindowSizes.mState.mMallocSizeOf);
15424 // Measurement of the following members may be added later if DMD finds it
15425 // is worthwhile:
15426 // - mMidasCommandManager
15427 // - many!
15430 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15431 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15432 aWindowSizes.mState.mMallocSizeOf(this);
15433 DocAddSizeOfExcludingThis(aWindowSizes);
15436 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15437 size_t* aNodeSize) const {
15438 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15439 // nsDocuments can only appear at the top of the DOM tree, and we use the
15440 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15441 // be called.
15442 MOZ_CRASH();
15445 /* static */
15446 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15447 size_t nodeSize = 0;
15448 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15450 // This is where we transfer the nodeSize obtained from
15451 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15452 switch (aNode.NodeType()) {
15453 case nsINode::ELEMENT_NODE:
15454 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15455 break;
15456 case nsINode::TEXT_NODE:
15457 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15458 break;
15459 case nsINode::CDATA_SECTION_NODE:
15460 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15461 break;
15462 case nsINode::COMMENT_NODE:
15463 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15464 break;
15465 default:
15466 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15467 break;
15470 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15471 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15474 if (aNode.IsContent()) {
15475 nsTArray<nsIContent*> anonKids;
15476 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15477 nsIContent::eAllChildren);
15478 for (nsIContent* anonKid : anonKids) {
15479 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15482 if (auto* element = Element::FromNode(aNode)) {
15483 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15484 AddSizeOfNodeTree(*shadow, aWindowSizes);
15489 // NOTE(emilio): If you feel smart and want to change this function to use
15490 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15491 // sane way, and kids of <content> won't point to the parent, so we'd never
15492 // find the root node where we should stop at.
15493 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15494 kid = kid->GetNextSibling()) {
15495 AddSizeOfNodeTree(*kid, aWindowSizes);
15499 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15500 ErrorResult& rv) {
15501 nsCOMPtr<nsIScriptGlobalObject> global =
15502 do_QueryInterface(aGlobal.GetAsSupports());
15503 if (!global) {
15504 rv.Throw(NS_ERROR_UNEXPECTED);
15505 return nullptr;
15508 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15509 do_QueryInterface(aGlobal.GetAsSupports());
15510 if (!prin) {
15511 rv.Throw(NS_ERROR_UNEXPECTED);
15512 return nullptr;
15515 nsCOMPtr<nsIURI> uri;
15516 NS_NewURI(getter_AddRefs(uri), "about:blank");
15517 if (!uri) {
15518 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15519 return nullptr;
15522 nsCOMPtr<Document> doc;
15523 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15524 nullptr, uri, uri, prin->GetPrincipal(),
15525 true, global, DocumentFlavorPlain);
15526 if (NS_FAILED(res)) {
15527 rv.Throw(res);
15528 return nullptr;
15531 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15533 return doc.forget();
15536 XPathExpression* Document::CreateExpression(const nsAString& aExpression,
15537 XPathNSResolver* aResolver,
15538 ErrorResult& rv) {
15539 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15542 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15543 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15546 already_AddRefed<XPathResult> Document::Evaluate(
15547 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15548 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15549 ErrorResult& rv) {
15550 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15551 aType, aResult, rv);
15554 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15555 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15556 if (!item) {
15557 return nullptr;
15559 nsCOMPtr<nsIDocShellTreeOwner> owner;
15560 item->GetTreeOwner(getter_AddRefs(owner));
15561 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15562 if (!appWin) {
15563 return nullptr;
15565 nsCOMPtr<nsIDocShell> appWinShell;
15566 appWin->GetDocShell(getter_AddRefs(appWinShell));
15567 if (!SameCOMIdentity(appWinShell, item)) {
15568 return nullptr;
15570 return appWin.forget();
15573 WindowContext* Document::GetTopLevelWindowContext() const {
15574 WindowContext* windowContext = GetWindowContext();
15575 return windowContext ? windowContext->TopWindowContext() : nullptr;
15578 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
15579 Document* parent;
15581 if (!mLoadedAsData) {
15582 parent = this;
15583 } else {
15584 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15585 if (!window) {
15586 return nullptr;
15589 parent = window->GetExtantDoc();
15590 if (!parent) {
15591 return nullptr;
15595 do {
15596 if (parent->IsTopLevelContentDocument()) {
15597 break;
15600 // If we ever have a non-content parent before we hit a toplevel content
15601 // parent, then we're never going to find one. Just bail.
15602 if (!parent->IsContentDocument()) {
15603 return nullptr;
15606 parent = parent->GetInProcessParentDocument();
15607 } while (parent);
15609 return parent;
15612 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
15613 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
15616 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
15617 MOZ_ASSERT(IsBeingUsedAsImage());
15618 MOZ_ASSERT(aReferencingDocument);
15620 if (!aReferencingDocument->mShouldReportUseCounters) {
15621 // No need to propagate use counters to a document that itself won't report
15622 // use counters.
15623 return;
15626 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15627 ("PropagateImageUseCounters from %s to %s",
15628 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
15629 nsContentUtils::TruncatedURLForDisplay(
15630 aReferencingDocument->mDocumentURI)
15631 .get()));
15633 if (aReferencingDocument->IsBeingUsedAsImage()) {
15634 NS_WARNING(
15635 "Page use counters from nested image documents may not "
15636 "propagate to the top-level document (bug 1657805)");
15639 SetCssUseCounterBits();
15640 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
15641 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
15644 bool Document::HasScriptsBlockedBySandbox() {
15645 return mSandboxFlags & SANDBOXED_SCRIPTS;
15648 // Some use-counter sanity-checking.
15649 static_assert(size_t(eUseCounter_EndCSSProperties) -
15650 size_t(eUseCounter_FirstCSSProperty) ==
15651 size_t(eCSSProperty_COUNT_with_aliases),
15652 "We should have the right amount of CSS property use counters");
15653 static_assert(size_t(eUseCounter_Count) -
15654 size_t(eUseCounter_FirstCountedUnknownProperty) ==
15655 size_t(CountedUnknownProperty::Count),
15656 "We should have the right amount of counted unknown properties"
15657 " use counters");
15658 static_assert(size_t(eUseCounter_Count) * 2 ==
15659 size_t(Telemetry::HistogramUseCounterCount),
15660 "There should be two histograms (document and page)"
15661 " for each use counter");
15663 #define ASSERT_CSS_COUNTER(id_, method_) \
15664 static_assert(size_t(eUseCounter_property_##method_) - \
15665 size_t(eUseCounter_FirstCSSProperty) == \
15666 size_t(id_), \
15667 "Order for CSS counters and CSS property id should match");
15668 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
15669 #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
15670 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
15671 #define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
15672 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
15673 #define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
15674 ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
15675 #include "mozilla/ServoCSSPropList.h"
15676 #undef CSS_PROP_ALIAS
15677 #undef CSS_PROP_SHORTHAND
15678 #undef CSS_PROP_LONGHAND
15679 #undef CSS_PROP_PUBLIC_OR_PRIVATE
15680 #undef ASSERT_CSS_COUNTER
15682 void Document::SetCssUseCounterBits() {
15683 if (StaticPrefs::layout_css_use_counters_enabled()) {
15684 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
15685 auto id = nsCSSPropertyID(i);
15686 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
15687 SetUseCounter(nsCSSProps::UseCounterFor(id));
15692 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
15693 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
15694 auto id = CountedUnknownProperty(i);
15695 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
15696 id)) {
15697 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
15703 void Document::InitUseCounters() {
15704 // We can be called more than once, e.g. when session history navigation shows
15705 // us a second time.
15706 if (mUseCountersInitialized) {
15707 return;
15709 mUseCountersInitialized = true;
15711 static_assert(Telemetry::HistogramUseCounterCount > 0);
15713 if (!ShouldIncludeInTelemetry(/* aAllowExtensionURIs = */ true)) {
15714 return;
15717 // Now we know for sure that we should report use counters from this document.
15718 mShouldReportUseCounters = true;
15720 WindowContext* top = GetWindowContextForPageUseCounters();
15721 if (!top) {
15722 // This is the case for SVG image documents. They are not displayed in a
15723 // window, but we still do want to record document use counters for them.
15725 // Page use counter propagation is handled in PropagateImageUseCounters,
15726 // so there is no need to use the cross-process machinery to send them.
15727 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15728 ("InitUseCounters for a non-displayed document [%s]",
15729 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
15730 return;
15733 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
15734 if (!wgc) {
15735 return;
15738 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15739 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
15740 " [from %s]",
15741 wgc->InnerWindowId(), top->Id(),
15742 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
15744 // Inform the parent process that we will send it page use counters later on.
15745 wgc->SendExpectPageUseCounters(top);
15746 mShouldSendPageUseCounters = true;
15749 // We keep separate counts for individual documents and top-level
15750 // pages to more accurately track how many web pages might break if
15751 // certain features were removed. Consider the case of a single
15752 // HTML document with several SVG images and/or iframes with
15753 // sub-documents of their own. If we maintained a single set of use
15754 // counters and all the sub-documents use a particular feature, then
15755 // telemetry would indicate that we would be breaking N documents if
15756 // that feature were removed. Whereas with a document/top-level
15757 // page split, we can see that N documents would be affected, but
15758 // only a single web page would be affected.
15760 // The difference between the values of these two histograms and the
15761 // related use counters below tell us how many pages did *not* use
15762 // the feature in question. For instance, if we see that a given
15763 // session has destroyed 30 content documents, but a particular use
15764 // counter shows only a count of 5, we can infer that the use
15765 // counter was *not* used in 25 of those 30 documents.
15767 // We do things this way, rather than accumulating a boolean flag
15768 // for each use counter, to avoid sending histograms for features
15769 // that don't get widely used. Doing things in this fashion means
15770 // smaller telemetry payloads and faster processing on the server
15771 // side.
15772 void Document::ReportDocumentUseCounters() {
15773 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
15774 return;
15777 mReportedDocumentUseCounters = true;
15779 // Note that a document is being destroyed. See the comment above for how
15780 // use counter histograms are interpreted relative to this measurement.
15781 // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
15782 // WindowGlobalParent::FinishAccumulatingPageUseCounters.
15783 Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
15785 // Ask all of our resource documents to report their own document use
15786 // counters.
15787 EnumerateExternalResources([](Document& aDoc) {
15788 aDoc.ReportDocumentUseCounters();
15789 return CallState::Continue;
15792 // Copy StyleUseCounters into our document use counters.
15793 SetCssUseCounterBits();
15795 // Report our per-document use counters.
15796 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15797 ("Reporting document use counters [%s]",
15798 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
15799 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
15800 auto uc = static_cast<UseCounter>(c);
15801 if (!mUseCounters[uc]) {
15802 continue;
15805 auto id = static_cast<Telemetry::HistogramID>(
15806 Telemetry::HistogramFirstUseCounter + uc * 2);
15807 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15808 (" > %s\n", Telemetry::GetHistogramName(id)));
15809 Telemetry::Accumulate(id, 1);
15812 ReportDocumentLazyLoadCounters();
15815 void Document::ReportDocumentLazyLoadCounters() {
15816 if (!mLazyLoadImageCount) {
15817 return;
15819 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_TOTAL, mLazyLoadImageCount);
15820 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_STARTED,
15821 mLazyLoadImageStarted);
15822 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_NOT_VIEWPORT,
15823 mLazyLoadImageStarted -
15824 mLazyLoadImageReachViewportLoading -
15825 mLazyLoadImageReachViewportLoaded);
15826 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_VIEWPORT_LOADING,
15827 mLazyLoadImageReachViewportLoading);
15828 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_VIEWPORT_LOADED,
15829 mLazyLoadImageReachViewportLoaded);
15832 void Document::SendPageUseCounters() {
15833 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
15834 return;
15837 // Ask all of our resource documents to send their own document use
15838 // counters to the parent process to be counted as page use counters.
15839 EnumerateExternalResources([](Document& aDoc) {
15840 aDoc.SendPageUseCounters();
15841 return CallState::Continue;
15844 // Send our use counters to the parent process to accumulate them towards the
15845 // page use counters for the top-level document.
15847 // We take our own document use counters (those in mUseCounters) and any child
15848 // document use counters (those in mChildDocumentUseCounters) that have been
15849 // explicitly propagated up to us, which includes resource documents, static
15850 // clones, and SVG images.
15851 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
15852 if (!wgc) {
15853 MOZ_ASSERT_UNREACHABLE(
15854 "SendPageUseCounters should be called while we still have access "
15855 "to our WindowContext");
15856 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15857 (" > too late to send page use counters"));
15858 return;
15861 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15862 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
15863 wgc->WindowContext()->Id(),
15864 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
15866 // Copy StyleUseCounters into our document use counters.
15867 SetCssUseCounterBits();
15869 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
15870 wgc->SendAccumulatePageUseCounters(counters);
15873 WindowContext* Document::GetWindowContextForPageUseCounters() const {
15874 if (mDisplayDocument) {
15875 // If we are a resource document, then go through it to find the
15876 // top-level document.
15877 return mDisplayDocument->GetWindowContextForPageUseCounters();
15880 if (mOriginalDocument) {
15881 // For static clones (print preview documents), contribute page use counters
15882 // towards the original document.
15883 return mOriginalDocument->GetWindowContextForPageUseCounters();
15886 WindowContext* wc = GetTopLevelWindowContext();
15887 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
15888 return nullptr;
15891 return wc;
15894 void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
15895 if (mIntersectionObservers.IsEmpty()) {
15896 return;
15899 DOMHighResTimeStamp time = 0;
15900 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
15901 if (Performance* perf = win->GetPerformance()) {
15902 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
15906 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
15907 mIntersectionObservers);
15908 for (const auto& observer : observers) {
15909 if (observer) {
15910 observer->Update(this, time);
15915 void Document::ScheduleIntersectionObserverNotification() {
15916 if (mIntersectionObservers.IsEmpty()) {
15917 return;
15919 MOZ_RELEASE_ASSERT(NS_IsMainThread());
15920 nsCOMPtr<nsIRunnable> notification =
15921 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
15922 &Document::NotifyIntersectionObservers);
15923 Dispatch(TaskCategory::Other, notification.forget());
15926 void Document::NotifyIntersectionObservers() {
15927 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
15928 mIntersectionObservers);
15929 for (const auto& observer : observers) {
15930 if (observer) {
15931 // MOZ_KnownLive because the 'observers' array guarantees to keep it
15932 // alive.
15933 MOZ_KnownLive(observer)->Notify();
15938 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserver() {
15939 if (!mLazyLoadImageObserver) {
15940 mLazyLoadImageObserver =
15941 DOMIntersectionObserver::CreateLazyLoadObserver(*this);
15943 return *mLazyLoadImageObserver;
15946 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserverViewport() {
15947 if (!mLazyLoadImageObserverViewport) {
15948 mLazyLoadImageObserverViewport =
15949 DOMIntersectionObserver::CreateLazyLoadObserverViewport(*this);
15951 return *mLazyLoadImageObserverViewport;
15954 void Document::IncLazyLoadImageReachViewport(bool aLoading) {
15955 if (aLoading) {
15956 ++mLazyLoadImageReachViewportLoading;
15957 } else {
15958 ++mLazyLoadImageReachViewportLoaded;
15962 void Document::NotifyLayerManagerRecreated() {
15963 NotifyActivityChanged();
15964 EnumerateSubDocuments([](Document& aSubDoc) {
15965 aSubDoc.NotifyLayerManagerRecreated();
15966 return CallState::Continue;
15970 XPathEvaluator* Document::XPathEvaluator() {
15971 if (!mXPathEvaluator) {
15972 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
15974 return mXPathEvaluator.get();
15977 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
15978 return mCachedEncoder.forget();
15981 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
15982 mCachedEncoder = aEncoder;
15985 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
15987 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
15989 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
15990 mStateObjectContainer = scContainer;
15991 mStateObjectCached.reset();
15994 bool Document::ComputeDocumentLWTheme() const {
15995 if (!NodePrincipal()->IsSystemPrincipal()) {
15996 return false;
15999 Element* element = GetRootElement();
16000 return element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
16001 nsGkAtoms::_true, eCaseMatters);
16004 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
16005 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
16006 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
16007 ELEMENT_NODE);
16008 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
16010 nsCOMPtr<Element> element;
16011 DebugOnly<nsresult> rv =
16012 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
16013 mozilla::dom::NOT_FROM_PARSER);
16015 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
16016 return element.forget();
16019 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
16020 BrowsingContext* aContext) {
16021 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
16022 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
16023 if (RefPtr<Document> doc = win->GetExtantDoc()) {
16024 SuppressDocument(doc);
16025 mDocuments.AppendElement(doc);
16031 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
16032 BrowsingContextGroup* aGroup) {
16033 for (const auto& bc : aGroup->Toplevels()) {
16034 SuppressBrowsingContext(bc);
16038 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
16039 SyncOperationBehavior aSyncBehavior)
16040 : mSyncBehavior(aSyncBehavior) {
16041 mMicroTaskLevel = 0;
16042 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16043 mMicroTaskLevel = ccjs->MicroTaskLevel();
16044 ccjs->SetMicroTaskLevel(0);
16046 if (aDoc) {
16047 mBrowsingContext = aDoc->GetBrowsingContext();
16048 if (InputTaskManager::CanSuspendInputEvent()) {
16049 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
16050 SuppressBrowsingContextGroup(bcg);
16052 } else if (mBrowsingContext) {
16053 SuppressBrowsingContext(mBrowsingContext->Top());
16055 if (mBrowsingContext &&
16056 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16057 InputTaskManager::CanSuspendInputEvent()) {
16058 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
16063 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
16064 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16065 win->TimeoutManager().BeginSyncOperation();
16067 aDoc->SetIsInSyncOperation(true);
16070 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
16071 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
16072 win->TimeoutManager().EndSyncOperation();
16074 aDoc->SetIsInSyncOperation(false);
16077 nsAutoSyncOperation::~nsAutoSyncOperation() {
16078 UnsuppressDocuments();
16079 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
16080 if (ccjs) {
16081 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
16083 if (mBrowsingContext &&
16084 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
16085 InputTaskManager::CanSuspendInputEvent()) {
16086 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
16090 void Document::SetIsInSyncOperation(bool aSync) {
16091 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
16092 ccjs->UpdateMicroTaskSuppressionGeneration();
16095 if (aSync) {
16096 ++mInSyncOperationCount;
16097 } else {
16098 --mInSyncOperationCount;
16102 gfxUserFontSet* Document::GetUserFontSet() {
16103 if (!mFontFaceSet) {
16104 return nullptr;
16107 return mFontFaceSet->GetUserFontSet();
16110 void Document::FlushUserFontSet() {
16111 if (!mFontFaceSetDirty) {
16112 return;
16115 mFontFaceSetDirty = false;
16117 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
16118 nsTArray<nsFontFaceRuleContainer> rules;
16119 RefPtr<PresShell> presShell = GetPresShell();
16120 if (presShell) {
16121 MOZ_ASSERT(mStyleSetFilled);
16122 mStyleSet->AppendFontFaceRules(rules);
16125 if (!mFontFaceSet && !rules.IsEmpty()) {
16126 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
16127 mFontFaceSet = new FontFaceSet(window, this);
16130 bool changed = false;
16131 if (mFontFaceSet) {
16132 changed = mFontFaceSet->UpdateRules(rules);
16135 // We need to enqueue a style change reflow (for later) to
16136 // reflect that we're modifying @font-face rules. (However,
16137 // without a reflow, nothing will happen to start any downloads
16138 // that are needed.)
16139 if (changed && presShell) {
16140 if (nsPresContext* presContext = presShell->GetPresContext()) {
16141 presContext->UserFontSetUpdated();
16147 void Document::MarkUserFontSetDirty() {
16148 if (mFontFaceSetDirty) {
16149 return;
16151 mFontFaceSetDirty = true;
16152 if (PresShell* presShell = GetPresShell()) {
16153 presShell->EnsureStyleFlush();
16157 FontFaceSet* Document::Fonts() {
16158 if (!mFontFaceSet) {
16159 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
16160 mFontFaceSet = new FontFaceSet(window, this);
16161 FlushUserFontSet();
16163 return mFontFaceSet;
16166 void Document::ReportHasScrollLinkedEffect(const TimeStamp& aTimeStamp) {
16167 MOZ_ASSERT(!aTimeStamp.IsNull());
16169 if (!mLastScrollLinkedEffectDetectionTime.IsNull() &&
16170 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) {
16171 return;
16174 if (mLastScrollLinkedEffectDetectionTime.IsNull()) {
16175 // Report to console just once.
16176 nsContentUtils::ReportToConsole(
16177 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
16178 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
16181 mLastScrollLinkedEffectDetectionTime = aTimeStamp;
16184 bool Document::HasScrollLinkedEffect() const {
16185 if (nsPresContext* pc = GetPresContext()) {
16186 return mLastScrollLinkedEffectDetectionTime ==
16187 pc->RefreshDriver()->MostRecentRefresh();
16190 return false;
16193 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
16194 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16195 // Setting has user interction on a discarded browsing context has
16196 // no effect.
16197 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
16201 bool Document::GetSHEntryHasUserInteraction() {
16202 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16203 return topWc->GetSHEntryHasUserInteraction();
16205 return false;
16208 void Document::SetUserHasInteracted() {
16209 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
16210 ("Document %p has been interacted by user.", this));
16212 // We maybe need to update the user-interaction permission.
16213 MaybeStoreUserInteractionAsPermission();
16215 // For purposes of reducing irrelevant session history entries on
16216 // the back button, we annotate entries with whether they had user
16217 // interaction. This is gated on its own flag on the WindowContext
16218 // (instead of mUserHasInteracted) to account for the fact that multiple
16219 // top-level SH entries can be associated with the same document.
16220 // Thus, whenever we create a new SH entry for this document,
16221 // this flag is reset.
16222 if (!GetSHEntryHasUserInteraction()) {
16223 nsIDocShell* docShell = this->GetDocShell();
16224 if (docShell) {
16225 nsCOMPtr<nsISHEntry> currentEntry;
16226 bool oshe;
16227 nsresult rv =
16228 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
16229 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
16230 currentEntry->SetHasUserInteraction(true);
16233 SetSHEntryHasUserInteraction(true);
16236 if (mUserHasInteracted) {
16237 return;
16240 mUserHasInteracted = true;
16242 if (mChannel) {
16243 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16244 loadInfo->SetDocumentHasUserInteracted(true);
16246 // Tell the parent process about user interaction
16247 if (auto* wgc = GetWindowGlobalChild()) {
16248 wgc->SendUpdateDocumentHasUserInteracted(true);
16251 MaybeAllowStorageForOpenerAfterUserInteraction();
16254 BrowsingContext* Document::GetBrowsingContext() const {
16255 nsCOMPtr<nsIDocShell> docshell(mDocumentContainer);
16256 return docshell ? docshell->GetBrowsingContext() : nullptr;
16259 void Document::NotifyUserGestureActivation() {
16260 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16261 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16262 WindowContext* windowContext = aBC->GetCurrentWindowContext();
16263 if (!windowContext) {
16264 return;
16267 nsIDocShell* docShell = aBC->GetDocShell();
16268 if (!docShell) {
16269 return;
16272 Document* document = docShell->GetDocument();
16273 if (!document) {
16274 return;
16277 // XXXedgar we probably could just check `IsInProcess()` after fission
16278 // enable.
16279 if (NodePrincipal()->Equals(document->NodePrincipal())) {
16280 windowContext->NotifyUserGestureActivation();
16284 for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
16285 if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
16286 windowContext->NotifyUserGestureActivation();
16292 bool Document::HasBeenUserGestureActivated() {
16293 RefPtr<WindowContext> wc = GetWindowContext();
16294 return wc && wc->HasBeenUserGestureActivated();
16297 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() {
16298 if (RefPtr<WindowContext> wc = GetWindowContext()) {
16299 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) {
16300 if (Performance* perf = innerWindow->GetPerformance()) {
16301 return perf->GetDOMTiming()->TimeStampToDOMHighRes(
16302 wc->GetUserGestureStart());
16307 NS_WARNING(
16308 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp");
16309 return 0;
16312 void Document::ClearUserGestureActivation() {
16313 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16314 bc = bc->Top();
16315 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16316 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
16317 windowContext->NotifyResetUserGestureActivation();
16323 bool Document::HasValidTransientUserGestureActivation() const {
16324 RefPtr<WindowContext> wc = GetWindowContext();
16325 return wc && wc->HasValidTransientUserGestureActivation();
16328 bool Document::ConsumeTransientUserGestureActivation() {
16329 RefPtr<WindowContext> wc = GetWindowContext();
16330 return wc && wc->ConsumeTransientUserGestureActivation();
16333 void Document::IncLazyLoadImageCount() {
16334 if (!mLazyLoadImageCount) {
16335 if (WindowContext* wc = GetTopLevelWindowContext()) {
16336 if (!wc->HadLazyLoadImage()) {
16337 Unused << wc->SetHadLazyLoadImage(true);
16341 ++mLazyLoadImageCount;
16344 void Document::SetDocTreeHadMedia() {
16345 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
16346 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
16347 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
16351 DocumentAutoplayPolicy Document::AutoplayPolicy() const {
16352 return AutoplayPolicy::IsAllowedToPlay(*this);
16355 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
16356 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16357 return;
16360 // This will probably change for project fission, but currently this document
16361 // and the opener are on the same process. In the future, we should make this
16362 // part async.
16363 nsPIDOMWindowInner* inner = GetInnerWindow();
16364 if (NS_WARN_IF(!inner)) {
16365 return;
16368 uint32_t cookieBehavior = CookieJarSettings()->GetCookieBehavior();
16369 if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
16370 cookieBehavior ==
16371 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
16372 // We care about first-party tracking resources only.
16373 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
16374 return;
16376 } else {
16377 MOZ_ASSERT(net::CookieJarSettings::IsRejectThirdPartyWithExceptions(
16378 cookieBehavior));
16381 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16382 if (NS_WARN_IF(!outer)) {
16383 return;
16386 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
16387 if (!openerBC) {
16388 // No opener.
16389 return;
16392 // We want to ensure the following check works for both fission mode and
16393 // non-fission mode:
16394 // "If the opener is not a 3rd party and if this window is not a 3rd party
16395 // with respect to the opener, we should not continue."
16397 // In non-fission mode, the opener and the opened window are in the same
16398 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16399 // In fission mode, if this window is not a 3rd party with respect to the
16400 // opener, they must be in the same process, so we can still use
16401 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16402 // party.
16403 if (openerBC->IsInProcess()) {
16404 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16405 if (NS_WARN_IF(!outerOpener)) {
16406 return;
16409 nsCOMPtr<nsPIDOMWindowInner> openerInner =
16410 outerOpener->GetCurrentInnerWindow();
16411 if (NS_WARN_IF(!openerInner)) {
16412 return;
16415 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
16416 if (NS_WARN_IF(!openerDocument)) {
16417 return;
16420 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
16421 if (NS_WARN_IF(!openerURI)) {
16422 return;
16425 // If the opener is not a 3rd party and if this window is not
16426 // a 3rd party with respect to the opener, we should not continue.
16427 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
16428 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
16429 return;
16433 // We don't care when the asynchronous work finishes here.
16434 Unused << StorageAccessAPIHelper::AllowAccessFor(
16435 NodePrincipal(), openerBC,
16436 ContentBlockingNotifier::eOpenerAfterUserInteraction);
16439 namespace {
16441 // Documents can stay alive for days. We don't want to update the permission
16442 // value at any user-interaction, and, using a timer triggered any X seconds
16443 // should be good enough. 'X' is taken from
16444 // privacy.userInteraction.document.interval pref.
16445 // We also want to store the user-interaction before shutting down, and, for
16446 // this reason, this class implements nsIAsyncShutdownBlocker interface.
16447 class UserInteractionTimer final : public Runnable,
16448 public nsITimerCallback,
16449 public nsIAsyncShutdownBlocker {
16450 public:
16451 NS_DECL_ISUPPORTS_INHERITED
16453 explicit UserInteractionTimer(Document* aDocument)
16454 : Runnable("UserInteractionTimer"),
16455 mPrincipal(aDocument->NodePrincipal()),
16456 mDocument(do_GetWeakReference(aDocument)) {
16457 static int32_t userInteractionTimerId = 0;
16458 // Blocker names must be unique. Let's create it now because when needed,
16459 // the document could be already gone.
16460 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
16461 ++userInteractionTimerId, aDocument);
16464 // Runnable interface
16466 NS_IMETHOD
16467 Run() override {
16468 uint32_t interval =
16469 StaticPrefs::privacy_userInteraction_document_interval();
16470 if (!interval) {
16471 return NS_OK;
16474 RefPtr<UserInteractionTimer> self = this;
16475 auto raii =
16476 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
16478 nsresult rv = NS_NewTimerWithCallback(
16479 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
16480 NS_ENSURE_SUCCESS(rv, NS_OK);
16482 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16483 NS_ENSURE_TRUE(!!phase, NS_OK);
16485 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
16486 __LINE__, u"UserInteractionTimer shutdown"_ns);
16487 NS_ENSURE_SUCCESS(rv, NS_OK);
16489 raii.release();
16490 return NS_OK;
16493 // nsITimerCallback interface
16495 NS_IMETHOD
16496 Notify(nsITimer* aTimer) override {
16497 StoreUserInteraction();
16498 return NS_OK;
16501 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
16502 using nsINamed::GetName;
16503 #endif
16505 // nsIAsyncShutdownBlocker interface
16507 NS_IMETHOD
16508 GetName(nsAString& aName) override {
16509 aName = mBlockerName;
16510 return NS_OK;
16513 NS_IMETHOD
16514 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
16515 CancelTimerAndStoreUserInteraction();
16516 return NS_OK;
16519 NS_IMETHOD
16520 GetState(nsIPropertyBag**) override { return NS_OK; }
16522 private:
16523 ~UserInteractionTimer() = default;
16525 void StoreUserInteraction() {
16526 // Remove the shutting down blocker
16527 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16528 if (phase) {
16529 phase->RemoveBlocker(this);
16532 // If the document is not gone, let's reset its timer flag.
16533 nsCOMPtr<Document> document = do_QueryReferent(mDocument);
16534 if (document) {
16535 ContentBlockingUserInteraction::Observe(mPrincipal);
16536 document->ResetUserInteractionTimer();
16540 void CancelTimerAndStoreUserInteraction() {
16541 if (mTimer) {
16542 mTimer->Cancel();
16543 mTimer = nullptr;
16546 StoreUserInteraction();
16549 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
16550 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
16551 NS_ENSURE_TRUE(!!svc, nullptr);
16553 nsCOMPtr<nsIAsyncShutdownClient> phase;
16554 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
16555 NS_ENSURE_SUCCESS(rv, nullptr);
16557 return phase.forget();
16560 nsCOMPtr<nsIPrincipal> mPrincipal;
16561 nsWeakPtr mDocument;
16563 nsCOMPtr<nsITimer> mTimer;
16565 nsString mBlockerName;
16568 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
16569 nsIAsyncShutdownBlocker)
16571 } // namespace
16573 void Document::MaybeStoreUserInteractionAsPermission() {
16574 // We care about user-interaction stored only for top-level documents
16575 // and documents with access to the Storage Access API
16576 if (!IsTopLevelContentDocument()) {
16577 bool hasSA;
16578 nsresult rv = HasStorageAccessSync(hasSA);
16579 if (NS_FAILED(rv) || !hasSA) {
16580 return;
16584 if (!mUserHasInteracted) {
16585 // First interaction, let's store this info now.
16586 ContentBlockingUserInteraction::Observe(NodePrincipal());
16587 return;
16590 if (mHasUserInteractionTimerScheduled) {
16591 return;
16594 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
16595 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
16596 EventQueuePriority::Idle);
16597 if (NS_WARN_IF(NS_FAILED(rv))) {
16598 return;
16601 // This value will be reset by the timer.
16602 mHasUserInteractionTimerScheduled = true;
16605 void Document::ResetUserInteractionTimer() {
16606 mHasUserInteractionTimerScheduled = false;
16609 bool Document::IsExtensionPage() const {
16610 return Preferences::GetBool("media.autoplay.allow-extension-background-pages",
16611 true) &&
16612 BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
16615 void Document::AddResizeObserver(ResizeObserver& aObserver) {
16616 if (!mResizeObserverController) {
16617 mResizeObserverController = MakeUnique<ResizeObserverController>(this);
16619 mResizeObserverController->AddResizeObserver(aObserver);
16622 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
16623 MOZ_DIAGNOSTIC_ASSERT(mResizeObserverController, "No controller?");
16624 if (MOZ_UNLIKELY(!mResizeObserverController)) {
16625 return;
16627 mResizeObserverController->RemoveResizeObserver(aObserver);
16630 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
16631 if (!mPermissionDelegateHandler) {
16632 mPermissionDelegateHandler =
16633 mozilla::MakeAndAddRef<PermissionDelegateHandler>(this);
16636 if (!mPermissionDelegateHandler->Initialize()) {
16637 mPermissionDelegateHandler = nullptr;
16640 return mPermissionDelegateHandler;
16643 void Document::ScheduleResizeObserversNotification() const {
16644 if (!mResizeObserverController) {
16645 return;
16648 mResizeObserverController->ScheduleNotification();
16651 void Document::ClearStaleServoData() {
16652 DocumentStyleRootIterator iter(this);
16653 while (Element* root = iter.GetNextStyleRoot()) {
16654 RestyleManager::ClearServoDataFromSubtree(root);
16658 Selection* Document::GetSelection(ErrorResult& aRv) {
16659 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
16660 if (!window) {
16661 return nullptr;
16664 if (!window->IsCurrentInnerWindow()) {
16665 return nullptr;
16668 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
16671 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
16672 // Step 1: check if cookie permissions are available or denied to this
16673 // document's principal
16674 nsCOMPtr<nsPIDOMWindowInner> inner = this->GetInnerWindow();
16675 if (!inner) {
16676 aHasStorageAccess = false;
16677 return NS_OK;
16679 Maybe<bool> resultBecauseCookiesApproved =
16680 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
16681 CookieJarSettings(), NodePrincipal());
16682 if (resultBecauseCookiesApproved.isSome()) {
16683 if (resultBecauseCookiesApproved.value()) {
16684 aHasStorageAccess = true;
16685 return NS_OK;
16686 } else {
16687 aHasStorageAccess = false;
16688 return NS_OK;
16692 // Step 2: Check if the browser settings determine whether or not this
16693 // document has access to its unpartitioned cookies.
16694 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
16695 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
16696 bool isOnThirdPartySkipList = false;
16697 if (mChannel) {
16698 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16699 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
16700 nsILoadInfo::StoragePermissionAllowListed;
16702 bool isThirdPartyTracker =
16703 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
16704 Maybe<bool> resultBecauseBrowserSettings =
16705 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
16706 CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
16707 isOnThirdPartySkipList, isThirdPartyTracker);
16708 if (resultBecauseBrowserSettings.isSome()) {
16709 if (resultBecauseBrowserSettings.value()) {
16710 aHasStorageAccess = true;
16711 return NS_OK;
16712 } else {
16713 aHasStorageAccess = false;
16714 return NS_OK;
16718 // Step 3: Check if the location of this call (embedded, top level, same-site)
16719 // determines if cookies are permitted or not.
16720 Maybe<bool> resultBecauseCallContext =
16721 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
16722 false);
16723 if (resultBecauseCallContext.isSome()) {
16724 if (resultBecauseCallContext.value()) {
16725 aHasStorageAccess = true;
16726 return NS_OK;
16727 } else {
16728 aHasStorageAccess = false;
16729 return NS_OK;
16733 // Step 4: Check if the permissions for this document determine if if has
16734 // access or is denied cookies.
16735 Maybe<bool> resultBecausePreviousPermission =
16736 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
16737 this);
16738 if (resultBecausePreviousPermission.isSome()) {
16739 if (resultBecausePreviousPermission.value()) {
16740 aHasStorageAccess = true;
16741 return NS_OK;
16742 } else {
16743 aHasStorageAccess = false;
16744 return NS_OK;
16747 // If you get here, we default to not giving you permission.
16748 aHasStorageAccess = false;
16749 return NS_OK;
16752 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
16753 mozilla::ErrorResult& aRv) {
16754 nsIGlobalObject* global = GetScopeObject();
16755 if (!global) {
16756 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
16757 return nullptr;
16760 RefPtr<Promise> promise =
16761 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
16762 if (aRv.Failed()) {
16763 return nullptr;
16766 bool hasStorageAccess;
16767 nsresult rv = HasStorageAccessSync(hasStorageAccess);
16768 if (NS_FAILED(rv)) {
16769 promise->MaybeRejectWithUndefined();
16770 } else {
16771 promise->MaybeResolve(hasStorageAccess);
16774 return promise.forget();
16777 RefPtr<Document::GetContentBlockingEventsPromise>
16778 Document::GetContentBlockingEvents() {
16779 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
16780 if (!inner) {
16781 return nullptr;
16784 RefPtr<WindowGlobalChild> wgc = inner->GetWindowGlobalChild();
16785 if (!wgc) {
16786 return nullptr;
16789 return wgc->SendGetContentBlockingEvents()->Then(
16790 GetCurrentSerialEventTarget(), __func__,
16791 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
16792 ResolveOrRejectValue& aValue) {
16793 if (aValue.IsResolve()) {
16794 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
16795 aValue.ResolveValue(), __func__);
16798 return Document::GetContentBlockingEventsPromise::CreateAndReject(
16799 false, __func__);
16803 RefPtr<MozPromise<int, bool, true>> Document::RequestStorageAccessAsyncHelper(
16804 nsPIDOMWindowInner* aInnerWindow, BrowsingContext* aBrowsingContext,
16805 nsIPrincipal* aPrincipal, bool aHasUserInteraction,
16806 ContentBlockingNotifier::StorageAccessPermissionGrantedReason aNotifier) {
16807 RefPtr<Document> self(this);
16808 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
16809 RefPtr<nsIPrincipal> principal(aPrincipal);
16811 // This is a lambda function that has some variables bound to it. It will be
16812 // called later in CompleteAllowAccessFor inside of AllowAccessFor.
16813 auto performFinalChecks = [inner, self, principal, aHasUserInteraction]() {
16814 // Create the user prompt
16815 RefPtr<StorageAccessAPIHelper::StorageAccessFinalCheckPromise::Private> p =
16816 new StorageAccessAPIHelper::StorageAccessFinalCheckPromise::Private(
16817 __func__);
16818 RefPtr<StorageAccessPermissionRequest> sapr =
16819 StorageAccessPermissionRequest::Create(
16820 inner, principal,
16821 // Allow
16822 [p] {
16823 Telemetry::AccumulateCategorical(
16824 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
16825 p->Resolve(StorageAccessAPIHelper::eAllow, __func__);
16827 // Block
16828 [p] {
16829 Telemetry::AccumulateCategorical(
16830 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
16831 p->Reject(false, __func__);
16834 using PromptResult = ContentPermissionRequestBase::PromptResult;
16835 PromptResult pr = sapr->CheckPromptPrefs();
16837 if (pr == PromptResult::Pending) {
16838 // We're about to show a prompt, record the request attempt
16839 Telemetry::AccumulateCategorical(
16840 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
16843 // Try to auto-grant the storage access so the user doesn't see the prompt.
16844 self->AutomaticStorageAccessPermissionCanBeGranted(aHasUserInteraction)
16845 ->Then(
16846 GetCurrentSerialEventTarget(), __func__,
16847 // If the autogrant check didn't fail, call this function
16848 [p, pr, sapr, inner](
16849 const Document::AutomaticStorageAccessPermissionGrantPromise::
16850 ResolveOrRejectValue& aValue) -> void {
16851 // Make a copy because we can't modified copy-captured lambda
16852 // variables.
16853 PromptResult pr2 = pr;
16855 // If the user didn't already click "allow" and we can autogrant,
16856 // do that!
16857 bool storageAccessCanBeGrantedAutomatically =
16858 aValue.IsResolve() && aValue.ResolveValue();
16859 bool autoGrant = false;
16860 if (pr2 == PromptResult::Pending &&
16861 storageAccessCanBeGrantedAutomatically) {
16862 pr2 = PromptResult::Granted;
16863 autoGrant = true;
16865 Telemetry::AccumulateCategorical(
16866 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
16867 AllowAutomatically);
16870 // If we can complete the permission request, do so.
16871 if (pr2 != PromptResult::Pending) {
16872 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
16873 pr2 == PromptResult::Denied);
16874 if (pr2 == PromptResult::Granted) {
16875 StorageAccessAPIHelper::StorageAccessPromptChoices choice =
16876 StorageAccessAPIHelper::eAllow;
16877 if (autoGrant) {
16878 choice = StorageAccessAPIHelper::eAllowAutoGrant;
16880 if (!autoGrant) {
16881 p->Resolve(choice, __func__);
16882 } else {
16883 sapr->MaybeDelayAutomaticGrants()->Then(
16884 GetCurrentSerialEventTarget(), __func__,
16885 [p, choice] { p->Resolve(choice, __func__); },
16886 [p] { p->Reject(false, __func__); });
16888 return;
16890 p->Reject(false, __func__);
16891 return;
16894 // If we get here, the auto-decision failed and we need to
16895 // wait for the user prompt to complete.
16896 sapr->RequestDelayedTask(
16897 inner->EventTargetFor(TaskCategory::Other),
16898 ContentPermissionRequestBase::DelayedTaskType::Request);
16901 return p;
16904 // Try to allow access for the given principal.
16905 return StorageAccessAPIHelper::AllowAccessFor(principal, aBrowsingContext,
16906 aNotifier, performFinalChecks);
16909 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
16910 mozilla::ErrorResult& aRv) {
16911 nsIGlobalObject* global = GetScopeObject();
16912 if (!global) {
16913 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
16914 return nullptr;
16917 RefPtr<Promise> promise = Promise::Create(global, aRv);
16918 if (aRv.Failed()) {
16919 return nullptr;
16922 // Step 0: Check that we have user activation before proceeding to prevent
16923 // rapid calls to the API to leak information.
16924 if (!HasValidTransientUserGestureActivation()) {
16925 // Report an error to the console for this case
16926 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
16927 nsLiteralCString("requestStorageAccess"),
16928 this, nsContentUtils::eDOM_PROPERTIES,
16929 "RequestStorageAccessUserGesture");
16930 this->ConsumeTransientUserGestureActivation();
16931 promise->MaybeRejectWithUndefined();
16932 return promise.forget();
16935 // Get a pointer to the inner window- We need this for convenience sake
16936 RefPtr<nsPIDOMWindowInner> inner = this->GetInnerWindow();
16937 if (!inner) {
16938 this->ConsumeTransientUserGestureActivation();
16939 promise->MaybeRejectWithUndefined();
16940 return promise.forget();
16943 // Step 1: Check if the principal calling this has a permission that lets
16944 // them use cookies or forbids them from using cookies.
16945 // This is outside of the spec of the StorageAccess API, but makes the return
16946 // values to have proper semantics.
16947 Maybe<bool> resultBecauseCookiesApproved =
16948 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
16949 CookieJarSettings(), NodePrincipal());
16950 if (resultBecauseCookiesApproved.isSome()) {
16951 if (resultBecauseCookiesApproved.value()) {
16952 promise->MaybeResolveWithUndefined();
16953 return promise.forget();
16954 } else {
16955 this->ConsumeTransientUserGestureActivation();
16956 promise->MaybeRejectWithUndefined();
16957 return promise.forget();
16961 // Step 2: Check if the browser settings always allow or deny cookies.
16962 // We should always return a resolved promise if the cookieBehavior is ACCEPT.
16963 // This is outside of the spec of the StorageAccess API, but makes the return
16964 // values to have proper semantics.
16965 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this);
16966 bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
16967 bool isOnThirdPartySkipList = false;
16968 if (mChannel) {
16969 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16970 isOnThirdPartySkipList = loadInfo->GetStoragePermission() ==
16971 nsILoadInfo::StoragePermissionAllowListed;
16973 bool isThirdPartyTracker =
16974 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner);
16975 Maybe<bool> resultBecauseBrowserSettings =
16976 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
16977 CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
16978 isOnThirdPartySkipList, isThirdPartyTracker);
16979 if (resultBecauseBrowserSettings.isSome()) {
16980 if (resultBecauseBrowserSettings.value()) {
16981 promise->MaybeResolveWithUndefined();
16982 return promise.forget();
16983 } else {
16984 this->ConsumeTransientUserGestureActivation();
16985 promise->MaybeRejectWithUndefined();
16986 return promise.forget();
16990 // Step 3: Check if the Document calling requestStorageAccess has anything to
16991 // gain from storage access. It should be embedded, non-null, etc.
16992 Maybe<bool> resultBecauseCallContext =
16993 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this,
16994 true);
16995 if (resultBecauseCallContext.isSome()) {
16996 if (resultBecauseCallContext.value()) {
16997 promise->MaybeResolveWithUndefined();
16998 return promise.forget();
16999 } else {
17000 this->ConsumeTransientUserGestureActivation();
17001 promise->MaybeRejectWithUndefined();
17002 return promise.forget();
17006 // Step 4: Check if we already allowed or denied storage access for this
17007 // document's storage key.
17008 Maybe<bool> resultBecausePreviousPermission =
17009 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI(
17010 this);
17011 if (resultBecausePreviousPermission.isSome()) {
17012 if (resultBecausePreviousPermission.value()) {
17013 promise->MaybeResolveWithUndefined();
17014 return promise.forget();
17015 } else {
17016 this->ConsumeTransientUserGestureActivation();
17017 promise->MaybeRejectWithUndefined();
17018 return promise.forget();
17022 // Get pointers to some objects that will be used in the async portion
17023 RefPtr<BrowsingContext> bc = this->GetBrowsingContext();
17024 RefPtr<nsGlobalWindowOuter> outer =
17025 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17026 if (!outer) {
17027 this->ConsumeTransientUserGestureActivation();
17028 promise->MaybeRejectWithUndefined();
17029 return promise.forget();
17031 RefPtr<Document> self(this);
17033 // Consume user activation before entering the async part of this method.
17034 // This prevents usage of other transient activation-gated APIs.
17035 this->ConsumeTransientUserGestureActivation();
17037 // Step 5. Start an async call to request storage access. This will either
17038 // perform an automatic decision or notify the user, then perform some follow
17039 // on work changing state to reflect the result of the API. If it resolves,
17040 // the request was granted. If it rejects it was denied.
17041 RequestStorageAccessAsyncHelper(inner, bc, NodePrincipal(), true,
17042 ContentBlockingNotifier::eStorageAccessAPI)
17043 ->Then(
17044 GetCurrentSerialEventTarget(), __func__,
17045 [self, inner, promise] {
17046 inner->SaveStorageAccessPermissionGranted();
17047 self->NotifyUserGestureActivation();
17048 promise->MaybeResolveWithUndefined();
17050 [promise] { promise->MaybeRejectWithUndefined(); });
17052 return promise.forget();
17055 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
17056 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
17057 mozilla::ErrorResult& aRv) {
17058 nsIGlobalObject* global = GetScopeObject();
17059 if (!global) {
17060 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17061 return nullptr;
17063 RefPtr<Promise> promise = Promise::Create(global, aRv);
17064 if (aRv.Failed()) {
17065 return nullptr;
17068 // Step 0: Check that we have user activation before proceeding to prevent
17069 // rapid calls to the API to leak information.
17070 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) {
17071 // Report an error to the console for this case
17072 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17073 nsLiteralCString("requestStorageAccess"),
17074 this, nsContentUtils::eDOM_PROPERTIES,
17075 "RequestStorageAccessUserGesture");
17076 this->ConsumeTransientUserGestureActivation();
17077 promise->MaybeRejectWithUndefined();
17078 return promise.forget();
17081 // Step 1: Check if the provided URI is different-site to this Document
17082 nsCOMPtr<nsIURI> thirdPartyURI;
17083 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
17084 if (NS_WARN_IF(NS_FAILED(rv))) {
17085 aRv.Throw(rv);
17086 return nullptr;
17088 bool isThirdPartyDocument;
17089 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument);
17090 if (NS_WARN_IF(NS_FAILED(rv))) {
17091 aRv.Throw(rv);
17092 return nullptr;
17094 bool isOnRejectForeignAllowList =
17095 RejectForeignAllowList::Check(thirdPartyURI);
17096 Maybe<bool> resultBecauseBrowserSettings =
17097 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
17098 CookieJarSettings(), isThirdPartyDocument, isOnRejectForeignAllowList,
17099 false, true);
17100 if (resultBecauseBrowserSettings.isSome()) {
17101 if (resultBecauseBrowserSettings.value()) {
17102 promise->MaybeResolveWithUndefined();
17103 return promise.forget();
17105 this->ConsumeTransientUserGestureActivation();
17106 promise->MaybeRejectWithUndefined();
17107 return promise.forget();
17110 // Step 2: Check that this Document is same-site to the top, and check that
17111 // we have user activation if we require it.
17112 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
17113 CheckSameSiteCallingContextDecidesStorageAccessAPI(
17114 this, aRequireUserActivation);
17115 if (resultBecauseCallContext.isSome()) {
17116 if (resultBecauseCallContext.value()) {
17117 promise->MaybeResolveWithUndefined();
17118 return promise.forget();
17120 this->ConsumeTransientUserGestureActivation();
17121 promise->MaybeRejectWithUndefined();
17122 return promise.forget();
17125 // Step 3: Get some useful variables that can be captured by the lambda for
17126 // the asynchronous portion
17127 RefPtr<BrowsingContext> bc = this->GetBrowsingContext();
17128 nsCOMPtr<nsPIDOMWindowInner> inner = this->GetInnerWindow();
17129 if (!inner) {
17130 this->ConsumeTransientUserGestureActivation();
17131 promise->MaybeRejectWithUndefined();
17132 return promise.forget();
17134 RefPtr<nsGlobalWindowOuter> outer =
17135 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
17136 if (!outer) {
17137 this->ConsumeTransientUserGestureActivation();
17138 promise->MaybeRejectWithUndefined();
17139 return promise.forget();
17141 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17142 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
17143 if (!principal) {
17144 this->ConsumeTransientUserGestureActivation();
17145 promise->MaybeRejectWithUndefined();
17146 return promise.forget();
17149 RefPtr<Document> self(this);
17150 bool hasUserActivation = HasValidTransientUserGestureActivation();
17152 // Consume user activation before entering the async part of this method.
17153 // This prevents usage of other transient activation-gated APIs.
17154 this->ConsumeTransientUserGestureActivation();
17156 // Step 4a: Start the async part of this function. Check the cookie
17157 // permission, but this can't be done in this process. We needs the cookie
17158 // permission of the URL as if it were embedded on this page, so we need to
17159 // make this check in the ContentParent.
17160 StorageAccessAPIHelper::AsyncCheckCookiesPermittedDecidesStorageAccessAPI(
17161 GetBrowsingContext(), principal)
17162 ->Then(
17163 GetCurrentSerialEventTarget(), __func__,
17164 [inner, thirdPartyURI, bc, principal, hasUserActivation, self,
17165 promise](Maybe<bool> cookieResult) {
17166 // Handle the result of the cookie permission check that took place
17167 // in the ContentParent.
17168 if (cookieResult.isSome()) {
17169 if (cookieResult.value()) {
17170 return MozPromise<int, bool, true>::CreateAndResolve(true,
17171 __func__);
17173 return MozPromise<int, bool, true>::CreateAndReject(false,
17174 __func__);
17177 // Step 4b: Check for the existing storage access permission
17178 nsAutoCString type;
17179 bool ok =
17180 AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
17181 if (!ok) {
17182 return MozPromise<int, bool, true>::CreateAndReject(false,
17183 __func__);
17185 if (AntiTrackingUtils::CheckStoragePermission(
17186 self->NodePrincipal(), type,
17187 nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
17188 return MozPromise<int, bool, true>::CreateAndResolve(true,
17189 __func__);
17192 // Step 4c: Try to request storage access, either automatically or
17193 // with a user-prompt. This is the part that is async in the
17194 // typical requestStorageAccess function.
17195 return self->RequestStorageAccessAsyncHelper(
17196 inner, bc, principal, hasUserActivation,
17197 ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI);
17199 // If the IPC rejects, we should reject our promise here which will
17200 // cause a rejection of the promise we already returned
17201 [promise]() {
17202 return MozPromise<int, bool, true>::CreateAndReject(false,
17203 __func__);
17205 ->Then(
17206 GetCurrentSerialEventTarget(), __func__,
17207 // If the previous handlers resolved, we should reinstate user
17208 // activation and resolve the promise we returned in Step 5.
17209 [self, inner, promise] {
17210 inner->SaveStorageAccessPermissionGranted();
17211 self->NotifyUserGestureActivation();
17212 promise->MaybeResolveWithUndefined();
17214 // If the previous handler rejected, we should reject the promise
17215 // returned by this function.
17216 [promise] { promise->MaybeRejectWithUndefined(); });
17218 // Step 5: While the async stuff is happening, we should return the promise so
17219 // our caller can continue executing.
17220 return promise.forget();
17223 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
17224 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
17225 // requestStorageAccessForOrigin may not require user activation. If we don't
17226 // have user activation at this point we should always show the prompt.
17227 if (!hasUserActivation ||
17228 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
17229 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
17230 false, __func__);
17232 if (XRE_IsContentProcess()) {
17233 // In the content process, we need to ask the parent process to compute
17234 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
17235 // isn't accessible in the content process.
17236 ContentChild* cc = ContentChild::GetSingleton();
17237 MOZ_ASSERT(cc);
17239 return cc
17240 ->SendAutomaticStorageAccessPermissionCanBeGranted(
17241 IPC::Principal(NodePrincipal()))
17242 ->Then(GetCurrentSerialEventTarget(), __func__,
17243 [](const ContentChild::
17244 AutomaticStorageAccessPermissionCanBeGrantedPromise::
17245 ResolveOrRejectValue& aValue) {
17246 if (aValue.IsResolve()) {
17247 return AutomaticStorageAccessPermissionGrantPromise::
17248 CreateAndResolve(aValue.ResolveValue(), __func__);
17251 return AutomaticStorageAccessPermissionGrantPromise::
17252 CreateAndReject(false, __func__);
17256 if (XRE_IsParentProcess()) {
17257 // In the parent process, we can directly compute this.
17258 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
17259 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
17260 __func__);
17263 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
17264 false, __func__);
17267 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
17268 nsIPrincipal* aPrincipal) {
17269 if (!StaticPrefs::dom_storage_access_auto_grants()) {
17270 return false;
17273 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
17274 return false;
17277 nsCOMPtr<nsIBrowserUsage> bu = do_ImportModule(
17278 "resource:///modules/BrowserUsageTelemetry.jsm", fallible);
17279 if (NS_WARN_IF(!bu)) {
17280 return false;
17283 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
17284 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours(
17285 &uniqueDomainsVisitedInPast24Hours);
17286 if (NS_WARN_IF(NS_FAILED(rv))) {
17287 return false;
17290 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo =
17291 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal);
17292 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) {
17293 return false;
17295 size_t originsThirdPartyHasAccessTo =
17296 maybeOriginsThirdPartyHasAccessTo.value();
17298 // one percent of the number of top-levels origins visited in the current
17299 // session (but not to exceed 24 hours), or the value of the
17300 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
17301 // higher.
17302 size_t maxConcurrentAutomaticGrants = std::max(
17303 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
17304 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
17307 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
17310 void Document::RecordNavigationTiming(ReadyState aReadyState) {
17311 if (!XRE_IsContentProcess()) {
17312 return;
17314 if (!IsTopLevelContentDocument()) {
17315 return;
17317 // If we dont have the timing yet (mostly because the doc is still loading),
17318 // get it from docshell.
17319 RefPtr<nsDOMNavigationTiming> timing = mTiming;
17320 if (!timing) {
17321 if (!mDocumentContainer) {
17322 return;
17324 timing = mDocumentContainer->GetNavigationTiming();
17325 if (!timing) {
17326 return;
17329 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
17330 switch (aReadyState) {
17331 case READYSTATE_LOADING:
17332 if (!mDOMLoadingSet) {
17333 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
17334 startTime);
17335 mDOMLoadingSet = true;
17337 break;
17338 case READYSTATE_INTERACTIVE:
17339 if (!mDOMInteractiveSet) {
17340 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
17341 startTime);
17342 mDOMInteractiveSet = true;
17344 break;
17345 case READYSTATE_COMPLETE:
17346 if (!mDOMCompleteSet) {
17347 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
17348 startTime);
17349 mDOMCompleteSet = true;
17351 break;
17352 default:
17353 NS_WARNING("Unexpected ReadyState value");
17354 break;
17358 bool Document::ModuleScriptsEnabled() {
17359 return nsContentUtils::IsChromeDoc(this) ||
17360 StaticPrefs::dom_moduleScripts_enabled();
17363 bool Document::ImportMapsEnabled() {
17364 return nsContentUtils::IsChromeDoc(this) ||
17365 StaticPrefs::dom_importMaps_enabled();
17368 void Document::ReportShadowDOMUsage() {
17369 nsPIDOMWindowInner* inner = GetInnerWindow();
17370 if (NS_WARN_IF(!inner)) {
17371 return;
17374 WindowContext* wc = inner->GetWindowContext();
17375 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
17376 return;
17379 WindowContext* topWc = wc->TopWindowContext();
17380 if (topWc->GetHasReportedShadowDOMUsage()) {
17381 return;
17384 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
17387 // static
17388 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
17389 return StaticPrefs::dom_storage_access_enabled() &&
17390 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
17393 bool Document::StorageAccessSandboxed() const {
17394 return Document::StorageAccessSandboxed(GetSandboxFlags());
17397 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
17398 if (mCachedTabSizeGeneration == 0 ||
17399 GetGeneration() != mCachedTabSizeGeneration) {
17400 return false;
17402 aSizes->mDom += mCachedTabSizes.mDom;
17403 aSizes->mStyle += mCachedTabSizes.mStyle;
17404 aSizes->mOther += mCachedTabSizes.mOther;
17405 return true;
17408 void Document::SetCachedSizes(nsTabSizes* aSizes) {
17409 mCachedTabSizes.mDom = aSizes->mDom;
17410 mCachedTabSizes.mStyle = aSizes->mStyle;
17411 mCachedTabSizes.mOther = aSizes->mOther;
17412 mCachedTabSizeGeneration = GetGeneration();
17415 already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
17416 nsAutoString contentLang;
17417 GetContentLanguage(contentLang);
17418 contentLang.StripWhitespace();
17420 // Content-Language may be a comma-separated list of language codes,
17421 // in which case the HTML5 spec says to treat it as unknown
17422 if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
17423 return NS_Atomize(contentLang);
17426 return nullptr;
17429 already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
17430 RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
17431 if (!lang) {
17432 lang = mLanguageFromCharset;
17434 return lang.forget();
17437 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
17438 nsAtom* aLanguage, bool* aNeedsToCache) const {
17439 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
17440 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
17443 void Document::DoCacheAllKnownLangPrefs() {
17444 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
17445 RefPtr<nsAtom> lang = GetLanguageForStyle();
17446 StaticPresData* data = StaticPresData::Get();
17447 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
17448 data->GetFontPrefsForLang(nsGkAtoms::x_math);
17449 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
17450 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
17451 for (const auto& key : mLanguagesUsed) {
17452 data->GetFontPrefsForLang(key);
17454 mMayNeedFontPrefsUpdate = false;
17457 void Document::RecomputeLanguageFromCharset() {
17458 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
17459 RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
17460 if (language == nsGkAtoms::Unicode) {
17461 language = service->GetLocaleLanguage();
17464 if (language == mLanguageFromCharset) {
17465 return;
17468 mMayNeedFontPrefsUpdate = true;
17469 mLanguageFromCharset = std::move(language);
17472 nsICookieJarSettings* Document::CookieJarSettings() {
17473 // If we are here, this is probably a javascript: URL document. In any case,
17474 // we must have a nsCookieJarSettings. Let's create it.
17475 if (!mCookieJarSettings) {
17476 Document* inProcessParent = GetInProcessParentDocument();
17478 mCookieJarSettings =
17479 inProcessParent
17480 ? net::CookieJarSettings::Create(
17481 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
17482 mozilla::net::CookieJarSettings::Cast(
17483 inProcessParent->CookieJarSettings())
17484 ->GetPartitionKey(),
17485 inProcessParent->CookieJarSettings()
17486 ->GetIsFirstPartyIsolated(),
17487 inProcessParent->CookieJarSettings()
17488 ->GetIsOnContentBlockingAllowList())
17489 : net::CookieJarSettings::Create(NodePrincipal());
17491 if (auto* wgc = GetWindowGlobalChild()) {
17492 net::CookieJarSettingsArgs csArgs;
17493 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
17494 // Update cookie settings in the parent process
17495 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
17496 NS_WARNING(
17497 "Failed to update document's cookie jar settings on the "
17498 "WindowGlobalParent");
17503 return mCookieJarSettings;
17506 bool Document::HasStorageAccessPermissionGranted() {
17507 // The HasStoragePermission flag in LoadInfo remains fixed when
17508 // it is set in the parent process, so we need to check the cache
17509 // to see if the permission is granted afterwards.
17510 nsPIDOMWindowInner* inner = GetInnerWindow();
17511 if (inner && inner->HasStorageAccessPermissionGranted()) {
17512 return true;
17515 if (!mChannel) {
17516 return false;
17519 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17520 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
17523 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
17524 // We only care about if the document gets the storage permission via the
17525 // allow list here. So we don't check the storage access cache in the inner
17526 // window.
17528 if (!mChannel) {
17529 return false;
17532 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17533 return loadInfo->GetStoragePermission() ==
17534 nsILoadInfo::StoragePermissionAllowListed;
17537 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
17538 nsPIDOMWindowInner* inner = GetInnerWindow();
17539 if (!inner) {
17540 return NodePrincipal();
17543 // Return our cached storage principal if one exists.
17544 if (mActiveStoragePrincipal) {
17545 return mActiveStoragePrincipal;
17548 // We use the lower-level ContentBlocking API here to ensure this
17549 // check doesn't send notifications.
17550 uint32_t rejectedReason = 0;
17551 if (ShouldAllowAccessFor(inner, GetDocumentURI(), &rejectedReason)) {
17552 return mActiveStoragePrincipal = NodePrincipal();
17555 // Let's use the storage principal only if we need to partition the cookie
17556 // jar. When the permission is granted, access will be different and the
17557 // normal principal will be used.
17558 if (ShouldPartitionStorage(rejectedReason) &&
17559 !StoragePartitioningEnabled(
17560 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
17561 return mActiveStoragePrincipal = NodePrincipal();
17564 return mActiveStoragePrincipal = mPartitionedPrincipal;
17567 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
17568 // If the document is sandboxed document or data: document, we should
17569 // get URI of the parent document.
17570 for (const Document* document = this;
17571 document && document->IsContentDocument();
17572 document = document->GetInProcessParentDocument()) {
17573 // The document URI may be about:blank even if it comes from actual web
17574 // site. Therefore, we need to check the URI of its principal.
17575 nsIPrincipal* principal = document->NodePrincipal();
17576 if (principal->GetIsNullPrincipal()) {
17577 continue;
17579 return principal;
17581 return nullptr;
17584 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
17585 mIsInitialDocumentInWindow = aIsInitialDocument;
17587 // Asynchronously tell the parent process that we are, or are no longer, the
17588 // initial document. This happens async.
17589 if (auto* wgc = GetWindowGlobalChild()) {
17590 wgc->SendSetIsInitialDocument(aIsInitialDocument);
17594 // static
17595 void Document::AddToplevelLoadingDocument(Document* aDoc) {
17596 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
17597 // Currently we're interested in foreground documents only, so bail out early.
17598 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
17599 return;
17602 if (!sLoadingForegroundTopLevelContentDocument) {
17603 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
17604 mozilla::ipc::IdleSchedulerChild* idleScheduler =
17605 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
17606 if (idleScheduler) {
17607 idleScheduler->SendRunningPrioritizedOperation();
17610 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
17611 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
17615 // static
17616 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
17617 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
17618 if (sLoadingForegroundTopLevelContentDocument) {
17619 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
17620 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
17621 delete sLoadingForegroundTopLevelContentDocument;
17622 sLoadingForegroundTopLevelContentDocument = nullptr;
17624 mozilla::ipc::IdleSchedulerChild* idleScheduler =
17625 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
17626 if (idleScheduler) {
17627 idleScheduler->SendPrioritizedOperationDone();
17633 ColorScheme Document::DefaultColorScheme() const {
17634 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
17637 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
17638 if (aIgnoreRFP == IgnoreRFP::No &&
17639 nsContentUtils::ShouldResistFingerprinting(this)) {
17640 return ColorScheme::Light;
17643 if (nsPresContext* pc = GetPresContext()) {
17644 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
17645 return *scheme;
17649 // NOTE(emilio): We use IsInChromeDocShell rather than IsChromeDoc
17650 // intentionally, to make chrome documents in content docshells (like about
17651 // pages) use the content color scheme.
17652 if (IsInChromeDocShell()) {
17653 return LookAndFeel::ColorSchemeForChrome();
17655 return LookAndFeel::PreferredColorSchemeForContent();
17658 bool Document::HasRecentlyStartedForegroundLoads() {
17659 if (!sLoadingForegroundTopLevelContentDocument) {
17660 return false;
17663 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
17664 ++i) {
17665 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
17666 // A page loaded in foreground could be in background now.
17667 if (!doc->IsInBackgroundWindow()) {
17668 nsPIDOMWindowInner* win = doc->GetInnerWindow();
17669 if (win) {
17670 Performance* perf = win->GetPerformance();
17671 if (perf &&
17672 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
17673 return true;
17679 // Didn't find any loading foreground documents, just clear the array.
17680 delete sLoadingForegroundTopLevelContentDocument;
17681 sLoadingForegroundTopLevelContentDocument = nullptr;
17683 mozilla::ipc::IdleSchedulerChild* idleScheduler =
17684 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
17685 if (idleScheduler) {
17686 idleScheduler->SendPrioritizedOperationDone();
17688 return false;
17691 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
17692 nsFrameLoader* aStaticCloneOf) {
17693 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
17694 clone->mElement = aElement;
17695 clone->mStaticCloneOf = aStaticCloneOf;
17698 bool Document::ShouldAvoidNativeTheme() const {
17699 return StaticPrefs::widget_non_native_theme_enabled() &&
17700 (!IsInChromeDocShell() || XRE_IsContentProcess());
17703 bool Document::UseRegularPrincipal() const {
17704 return EffectiveStoragePrincipal() == NodePrincipal();
17707 bool Document::HasThirdPartyChannel() {
17708 nsCOMPtr<nsIChannel> channel = GetChannel();
17709 if (channel) {
17710 // We assume that the channel is a third-party by default.
17711 bool thirdParty = true;
17713 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
17714 components::ThirdPartyUtil::Service();
17715 if (!thirdPartyUtil) {
17716 return thirdParty;
17719 // Check that if the channel is a third-party to its parent.
17720 nsresult rv =
17721 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
17722 if (NS_FAILED(rv)) {
17723 // Assume third-party in case of failure
17724 thirdParty = true;
17727 return thirdParty;
17730 if (mParentDocument) {
17731 return mParentDocument->HasThirdPartyChannel();
17734 return false;
17737 bool Document::ShouldIncludeInTelemetry(bool aAllowExtensionURIs) {
17738 if (!(IsContentDocument() || IsResourceDoc())) {
17739 return false;
17742 if (!aAllowExtensionURIs &&
17743 NodePrincipal()->GetIsAddonOrExpandedAddonPrincipal()) {
17744 return false;
17747 return !NodePrincipal()->SchemeIs("about") &&
17748 !NodePrincipal()->SchemeIs("chrome") &&
17749 !NodePrincipal()->SchemeIs("resource");
17752 void Document::GetConnectedShadowRoots(
17753 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
17754 AppendToArray(aOut, mComposedShadowRoots);
17757 bool Document::HasPictureInPictureChildElement() const {
17758 return mPictureInPictureChildElementCount > 0;
17761 void Document::EnableChildElementInPictureInPictureMode() {
17762 mPictureInPictureChildElementCount++;
17763 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
17766 void Document::DisableChildElementInPictureInPictureMode() {
17767 mPictureInPictureChildElementCount--;
17768 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
17771 void Document::AddMediaElementWithMSE() {
17772 if (mMediaElementWithMSECount++ == 0) {
17773 WindowGlobalChild* wgc = GetWindowGlobalChild();
17774 if (wgc) {
17775 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
17780 void Document::RemoveMediaElementWithMSE() {
17781 MOZ_ASSERT(mMediaElementWithMSECount > 0);
17782 if (--mMediaElementWithMSECount == 0) {
17783 WindowGlobalChild* wgc = GetWindowGlobalChild();
17784 if (wgc) {
17785 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
17790 void Document::UnregisterFromMemoryReportingForDataDocument() {
17791 if (!mAddedToMemoryReportingAsDataDocument) {
17792 return;
17794 mAddedToMemoryReportingAsDataDocument = false;
17795 nsIGlobalObject* global = GetScopeObject();
17796 if (global) {
17797 if (nsPIDOMWindowInner* win = global->AsInnerWindow()) {
17798 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
17799 this);
17803 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
17804 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
17805 mOOPChildrenLoading.AppendElement(aChild);
17806 if (mOOPChildrenLoading.Length() == 1) {
17807 // Let's block unload so that we're blocked from going into the BFCache
17808 // until the child has actually notified us that it has done loading.
17809 BlockOnload();
17813 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
17814 // aChild will not be in the list if nsDocLoader::Stop() was called, since
17815 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
17816 // so we don't need to call DocLoaderIsEmpty in that case.
17817 if (mOOPChildrenLoading.RemoveElement(aChild)) {
17818 if (mOOPChildrenLoading.IsEmpty()) {
17819 UnblockOnload(false);
17821 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
17822 if (docLoader) {
17823 docLoader->OOPChildrenLoadingIsEmpty();
17828 void Document::ClearOOPChildrenLoading() {
17829 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
17830 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
17831 if (!oopChildrenLoading.IsEmpty()) {
17832 UnblockOnload(false);
17836 } // namespace mozilla::dom