Bug 1727271: part 3) const-qualify `Document::HasValidTransientUserGestureActivation...
[gecko.git] / dom / base / Document.cpp
blob032a746524e1c2f325ad3b2177ad23441bc449c0
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/ContentBlocking.h"
53 #include "mozilla/ContentBlockingAllowList.h"
54 #include "mozilla/ContentBlockingNotifier.h"
55 #include "mozilla/ContentBlockingUserInteraction.h"
56 #include "mozilla/CycleCollectedJSContext.h"
57 #include "mozilla/DebugOnly.h"
58 #include "mozilla/DocLoadingTimelineMarker.h"
59 #include "mozilla/DocumentStyleRootIterator.h"
60 #include "mozilla/EditorBase.h"
61 #include "mozilla/EditorCommands.h"
62 #include "mozilla/Encoding.h"
63 #include "mozilla/ErrorResult.h"
64 #include "mozilla/EventDispatcher.h"
65 #include "mozilla/EventListenerManager.h"
66 #include "mozilla/EventQueue.h"
67 #include "mozilla/EventStateManager.h"
68 #include "mozilla/ExtensionPolicyService.h"
69 #include "mozilla/FullscreenChange.h"
70 #include "mozilla/GlobalStyleSheetCache.h"
71 #include "mozilla/HTMLEditor.h"
72 #include "mozilla/HoldDropJSObjects.h"
73 #include "mozilla/IdentifierMapEntry.h"
74 #include "mozilla/InputTaskManager.h"
75 #include "mozilla/IntegerRange.h"
76 #include "mozilla/InternalMutationEvent.h"
77 #include "mozilla/Likely.h"
78 #include "mozilla/Logging.h"
79 #include "mozilla/LookAndFeel.h"
80 #include "mozilla/MacroForEach.h"
81 #include "mozilla/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/RelativeTo.h"
99 #include "mozilla/RestyleManager.h"
100 #include "mozilla/ReverseIterator.h"
101 #include "mozilla/SMILAnimationController.h"
102 #include "mozilla/SMILTimeContainer.h"
103 #include "mozilla/ScopeExit.h"
104 #include "mozilla/Components.h"
105 #include "mozilla/ServoCSSPropList.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/SVGSVGElement.h"
209 #include "mozilla/dom/SVGUseElement.h"
210 #include "mozilla/dom/ScriptLoader.h"
211 #include "mozilla/dom/ScriptSettings.h"
212 #include "mozilla/dom/Selection.h"
213 #include "mozilla/dom/ServiceWorkerContainer.h"
214 #include "mozilla/dom/ServiceWorkerDescriptor.h"
215 #include "mozilla/dom/ServiceWorkerManager.h"
216 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
217 #include "mozilla/dom/ShadowRoot.h"
218 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
219 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
220 #include "mozilla/dom/StyleSheetList.h"
221 #include "mozilla/dom/TimeoutManager.h"
222 #include "mozilla/dom/Touch.h"
223 #include "mozilla/dom/TouchEvent.h"
224 #include "mozilla/dom/TreeOrderedArray.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/XPathEvaluator.h"
234 #include "mozilla/dom/nsCSPContext.h"
235 #include "mozilla/dom/nsCSPUtils.h"
236 #include "mozilla/extensions/WebExtensionPolicy.h"
237 #include "mozilla/fallible.h"
238 #include "mozilla/gfx/BaseCoord.h"
239 #include "mozilla/gfx/BaseSize.h"
240 #include "mozilla/gfx/Coord.h"
241 #include "mozilla/gfx/Point.h"
242 #include "mozilla/gfx/ScaleFactor.h"
243 #include "mozilla/intl/LocaleService.h"
244 #include "mozilla/ipc/IdleSchedulerChild.h"
245 #include "mozilla/ipc/MessageChannel.h"
246 #include "mozilla/net/ChannelEventQueue.h"
247 #include "mozilla/net/CookieJarSettings.h"
248 #include "mozilla/net/NeckoChannelParams.h"
249 #include "mozilla/net/RequestContextService.h"
250 #include "nsAboutProtocolUtils.h"
251 #include "nsAlgorithm.h"
252 #include "nsAttrValue.h"
253 #include "nsAttrValueInlines.h"
254 #include "nsBaseHashtable.h"
255 #include "nsBidiUtils.h"
256 #include "nsCRT.h"
257 #include "nsCSSPropertyID.h"
258 #include "nsCSSProps.h"
259 #include "nsCSSPseudoElements.h"
260 #include "nsCanvasFrame.h"
261 #include "nsCaseTreatment.h"
262 #include "nsCharsetSource.h"
263 #include "nsCommandManager.h"
264 #include "nsCommandParams.h"
265 #include "nsComponentManagerUtils.h"
266 #include "nsContentCreatorFunctions.h"
267 #include "nsContentList.h"
268 #include "nsContentPermissionHelper.h"
269 #include "nsContentSecurityUtils.h"
270 #include "nsContentUtils.h"
271 #include "nsCoord.h"
272 #include "nsCycleCollectionNoteChild.h"
273 #include "nsCycleCollectionTraversalCallback.h"
274 #include "nsDOMAttributeMap.h"
275 #include "nsDOMCaretPosition.h"
276 #include "nsDOMNavigationTiming.h"
277 #include "nsDOMString.h"
278 #include "nsDeviceContext.h"
279 #include "nsDocShell.h"
280 #include "nsError.h"
281 #include "nsEscape.h"
282 #include "nsFocusManager.h"
283 #include "nsFrameLoader.h"
284 #include "nsFrameLoaderOwner.h"
285 #include "nsGenericHTMLElement.h"
286 #include "nsGlobalWindowInner.h"
287 #include "nsGlobalWindowOuter.h"
288 #include "nsHTMLCSSStyleSheet.h"
289 #include "nsHTMLDocument.h"
290 #include "nsHTMLStyleSheet.h"
291 #include "nsHtml5Module.h"
292 #include "nsHtml5Parser.h"
293 #include "nsHtml5TreeOpExecutor.h"
294 #include "nsIAsyncShutdown.h"
295 #include "nsIAuthPrompt.h"
296 #include "nsIAuthPrompt2.h"
297 #include "nsIBFCacheEntry.h"
298 #include "nsIBaseWindow.h"
299 #include "nsIBrowserChild.h"
300 #include "nsIBrowserUsage.h"
301 #include "nsICSSLoaderObserver.h"
302 #include "nsICategoryManager.h"
303 #include "nsICertOverrideService.h"
304 #include "nsIContent.h"
305 #include "nsIContentPolicy.h"
306 #include "nsIContentSecurityPolicy.h"
307 #include "nsIContentSink.h"
308 #include "nsICookieJarSettings.h"
309 #include "nsICookieService.h"
310 #include "nsIDOMXULCommandDispatcher.h"
311 #include "nsIDocShell.h"
312 #include "nsIDocShellTreeItem.h"
313 #include "nsIDocumentActivity.h"
314 #include "nsIDocumentEncoder.h"
315 #include "nsIDocumentLoader.h"
316 #include "nsIDocumentLoaderFactory.h"
317 #include "nsIDocumentObserver.h"
318 #include "nsIEditingSession.h"
319 #include "nsIEditor.h"
320 #include "nsIEffectiveTLDService.h"
321 #include "nsIFile.h"
322 #include "nsIFileChannel.h"
323 #include "nsIFrame.h"
324 #include "nsIGlobalObject.h"
325 #include "nsIHTMLCollection.h"
326 #include "nsIHttpChannel.h"
327 #include "nsIHttpChannelInternal.h"
328 #include "nsIIOService.h"
329 #include "nsIImageLoadingContent.h"
330 #include "nsIInlineSpellChecker.h"
331 #include "nsIInputStreamChannel.h"
332 #include "nsIInterfaceRequestorUtils.h"
333 #include "nsILayoutHistoryState.h"
334 #include "nsIMultiPartChannel.h"
335 #include "nsIMutationObserver.h"
336 #include "nsINSSErrorsService.h"
337 #include "nsINamed.h"
338 #include "nsINodeList.h"
339 #include "nsIObjectLoadingContent.h"
340 #include "nsIObserverService.h"
341 #include "nsIPermission.h"
342 #include "nsIPrompt.h"
343 #include "nsIPropertyBag2.h"
344 #include "nsIPublicKeyPinningService.h"
345 #include "nsIReferrerInfo.h"
346 #include "nsIRefreshURI.h"
347 #include "nsIRequest.h"
348 #include "nsIRequestContext.h"
349 #include "nsIRunnable.h"
350 #include "nsISHEntry.h"
351 #include "nsIScriptElement.h"
352 #include "nsIScriptError.h"
353 #include "nsIScriptGlobalObject.h"
354 #include "nsIScriptSecurityManager.h"
355 #include "nsISecurityConsoleMessage.h"
356 #include "nsISelectionController.h"
357 #include "nsISerialEventTarget.h"
358 #include "nsISerializable.h"
359 #include "nsISimpleEnumerator.h"
360 #include "nsISiteSecurityService.h"
361 #include "nsISocketProvider.h"
362 #include "nsISpeculativeConnect.h"
363 #include "nsIStructuredCloneContainer.h"
364 #include "nsIThread.h"
365 #include "nsITimedChannel.h"
366 #include "nsITimer.h"
367 #include "nsITransportSecurityInfo.h"
368 #include "nsIURIMutator.h"
369 #include "nsIVariant.h"
370 #include "nsIWeakReference.h"
371 #include "nsIWebNavigation.h"
372 #include "nsIWidget.h"
373 #include "nsIX509Cert.h"
374 #include "nsIX509CertValidity.h"
375 #include "nsIXMLContentSink.h"
376 #include "nsIXULRuntime.h"
377 #include "nsImageLoadingContent.h"
378 #include "nsImportModule.h"
379 #include "nsLanguageAtomService.h"
380 #include "nsLayoutUtils.h"
381 #include "nsNetCID.h"
382 #include "nsNetUtil.h"
383 #include "nsNodeInfoManager.h"
384 #include "nsObjectLoadingContent.h"
385 #include "nsPIDOMWindowInlines.h"
386 #include "nsPIWindowRoot.h"
387 #include "nsPoint.h"
388 #include "nsPointerHashKeys.h"
389 #include "nsPresContext.h"
390 #include "nsQueryFrame.h"
391 #include "nsQueryObject.h"
392 #include "nsRange.h"
393 #include "nsRect.h"
394 #include "nsRefreshDriver.h"
395 #include "nsSandboxFlags.h"
396 #include "nsSerializationHelper.h"
397 #include "nsServiceManagerUtils.h"
398 #include "nsStringFlags.h"
399 #include "nsStringIterator.h"
400 #include "nsStyleSheetService.h"
401 #include "nsStyleStruct.h"
402 #include "nsTextNode.h"
403 #include "nsUnicharUtils.h"
404 #include "nsWrapperCache.h"
405 #include "nsWrapperCacheInlines.h"
406 #include "nsXPCOMCID.h"
407 #include "nsXULAppAPI.h"
408 #include "prthread.h"
409 #include "prtime.h"
410 #include "prtypes.h"
411 #include "xpcpublic.h"
413 // XXX Must be included after mozilla/Encoding.h
414 #include "encoding_rs.h"
416 #ifdef MOZ_XUL
417 # include "mozilla/dom/XULBroadcastManager.h"
418 # include "mozilla/dom/XULPersist.h"
419 # include "nsIAppWindow.h"
420 # include "nsXULPrototypeDocument.h"
421 # include "nsXULCommandDispatcher.h"
422 # include "nsXULPopupManager.h"
423 # include "nsIDocShellTreeOwner.h"
424 #endif
426 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
427 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
428 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
429 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
431 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
433 mozilla::LazyLogModule gPageCacheLog("PageCache");
434 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
435 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
436 mozilla::LazyLogModule gUseCountersLog("UseCounters");
438 namespace mozilla {
439 namespace dom {
441 using LinkArray = nsTArray<Link*>;
443 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
444 nullptr;
446 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
447 static LazyLogModule gCspPRLog("CSP");
448 LazyLogModule gUserInteractionPRLog("UserInteraction");
450 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
451 nsIHttpChannel** aHttpChannel) {
452 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
453 if (httpChannel) {
454 httpChannel.forget(aHttpChannel);
455 return NS_OK;
458 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
459 if (!multipart) {
460 *aHttpChannel = nullptr;
461 return NS_OK;
464 nsCOMPtr<nsIChannel> baseChannel;
465 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
466 if (NS_WARN_IF(NS_FAILED(rv))) {
467 return rv;
470 httpChannel = do_QueryInterface(baseChannel);
471 httpChannel.forget(aHttpChannel);
473 return NS_OK;
476 } // namespace dom
478 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
480 IdentifierMapEntry::IdentifierMapEntry(
481 const IdentifierMapEntry::DependentAtomOrString* aKey)
482 : mKey(aKey ? *aKey : nullptr) {}
484 void IdentifierMapEntry::Traverse(
485 nsCycleCollectionTraversalCallback* aCallback) {
486 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
487 "mIdentifierMap mNameContentList");
488 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
490 if (mImageElement) {
491 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
492 "mIdentifierMap mImageElement element");
493 nsIContent* imageElement = mImageElement;
494 aCallback->NoteXPCOMChild(imageElement);
498 bool IdentifierMapEntry::IsEmpty() {
499 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
500 !mImageElement;
503 bool IdentifierMapEntry::HasNameElement() const {
504 return mNameContentList && mNameContentList->Length() != 0;
507 void IdentifierMapEntry::AddContentChangeCallback(
508 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
509 if (!mChangeCallbacks) {
510 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
513 ChangeCallback cc = {aCallback, aData, aForImage};
514 mChangeCallbacks->PutEntry(cc);
517 void IdentifierMapEntry::RemoveContentChangeCallback(
518 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
519 if (!mChangeCallbacks) return;
520 ChangeCallback cc = {aCallback, aData, aForImage};
521 mChangeCallbacks->RemoveEntry(cc);
522 if (mChangeCallbacks->Count() == 0) {
523 mChangeCallbacks = nullptr;
527 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
528 Element* aNewElement,
529 bool aImageOnly) {
530 if (!mChangeCallbacks) return;
532 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
533 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
534 // Don't fire image changes for non-image observers, and don't fire element
535 // changes for image observers when an image override is active.
536 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
537 continue;
540 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
541 iter.Remove();
546 void IdentifierMapEntry::AddIdElement(Element* aElement) {
547 MOZ_ASSERT(aElement, "Must have element");
548 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
550 size_t index = mIdContentList.Insert(*aElement);
551 if (index == 0) {
552 Element* oldElement = mIdContentList->SafeElementAt(1);
553 FireChangeCallbacks(oldElement, aElement);
557 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
558 MOZ_ASSERT(aElement, "Missing element");
560 // This should only be called while the document is in an update.
561 // Assertions near the call to this method guarantee this.
563 // This could fire in OOM situations
564 // Only assert this in HTML documents for now as XUL does all sorts of weird
565 // crap.
566 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
567 mIdContentList->Contains(aElement),
568 "Removing id entry that doesn't exist");
570 // XXXbz should this ever Compact() I guess when all the content is gone
571 // we'll just get cleaned up in the natural order of things...
572 Element* currentElement = mIdContentList->SafeElementAt(0);
573 mIdContentList.RemoveElement(*aElement);
574 if (currentElement == aElement) {
575 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
579 void IdentifierMapEntry::SetImageElement(Element* aElement) {
580 Element* oldElement = GetImageIdElement();
581 mImageElement = aElement;
582 Element* newElement = GetImageIdElement();
583 if (oldElement != newElement) {
584 FireChangeCallbacks(oldElement, newElement, true);
588 void IdentifierMapEntry::ClearAndNotify() {
589 Element* currentElement = mIdContentList->SafeElementAt(0);
590 mIdContentList.Clear();
591 if (currentElement) {
592 FireChangeCallbacks(currentElement, nullptr);
594 mNameContentList = nullptr;
595 if (mImageElement) {
596 SetImageElement(nullptr);
598 mChangeCallbacks = nullptr;
601 namespace dom {
603 class SimpleHTMLCollection final : public nsSimpleContentList,
604 public nsIHTMLCollection {
605 public:
606 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
608 NS_DECL_ISUPPORTS_INHERITED
610 virtual nsINode* GetParentObject() override {
611 return nsSimpleContentList::GetParentObject();
613 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
614 virtual Element* GetElementAt(uint32_t aIndex) override {
615 return mElements.SafeElementAt(aIndex)->AsElement();
618 virtual Element* GetFirstNamedElement(const nsAString& aName,
619 bool& aFound) override {
620 aFound = false;
621 RefPtr<nsAtom> name = NS_Atomize(aName);
622 for (uint32_t i = 0; i < mElements.Length(); i++) {
623 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
624 Element* element = mElements[i]->AsElement();
625 if (element->GetID() == name ||
626 (element->HasName() &&
627 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
628 aFound = true;
629 return element;
632 return nullptr;
635 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
636 AutoTArray<nsAtom*, 8> atoms;
637 for (uint32_t i = 0; i < mElements.Length(); i++) {
638 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
639 Element* element = mElements[i]->AsElement();
641 nsAtom* id = element->GetID();
642 MOZ_ASSERT(id != nsGkAtoms::_empty);
643 if (id && !atoms.Contains(id)) {
644 atoms.AppendElement(id);
647 if (element->HasName()) {
648 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
649 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
650 if (name && !atoms.Contains(name)) {
651 atoms.AppendElement(name);
656 nsString* names = aNames.AppendElements(atoms.Length());
657 for (uint32_t i = 0; i < atoms.Length(); i++) {
658 atoms[i]->ToString(names[i]);
662 virtual JSObject* GetWrapperPreserveColorInternal() override {
663 return nsWrapperCache::GetWrapperPreserveColor();
665 virtual void PreserveWrapperInternal(
666 nsISupports* aScriptObjectHolder) override {
667 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
669 virtual JSObject* WrapObject(JSContext* aCx,
670 JS::Handle<JSObject*> aGivenProto) override {
671 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
674 using nsBaseContentList::Item;
676 private:
677 virtual ~SimpleHTMLCollection() = default;
680 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
681 nsIHTMLCollection)
683 } // namespace dom
685 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
686 if (!mNameContentList) {
687 mNameContentList = new dom::SimpleHTMLCollection(aNode);
690 mNameContentList->AppendElement(aElement);
693 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
694 if (mNameContentList) {
695 mNameContentList->RemoveElement(aElement);
699 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
700 Element* idElement = GetIdElement();
701 return idElement &&
702 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
705 size_t IdentifierMapEntry::SizeOfExcludingThis(
706 MallocSizeOf aMallocSizeOf) const {
707 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
710 // Helper structs for the content->subdoc map
712 class SubDocMapEntry : public PLDHashEntryHdr {
713 public:
714 // Both of these are strong references
715 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
716 dom::Document* mSubDocument;
719 class OnloadBlocker final : public nsIRequest {
720 public:
721 OnloadBlocker() = default;
723 NS_DECL_ISUPPORTS
724 NS_DECL_NSIREQUEST
726 private:
727 ~OnloadBlocker() = default;
730 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
732 NS_IMETHODIMP
733 OnloadBlocker::GetName(nsACString& aResult) {
734 aResult.AssignLiteral("about:document-onload-blocker");
735 return NS_OK;
738 NS_IMETHODIMP
739 OnloadBlocker::IsPending(bool* _retval) {
740 *_retval = true;
741 return NS_OK;
744 NS_IMETHODIMP
745 OnloadBlocker::GetStatus(nsresult* status) {
746 *status = NS_OK;
747 return NS_OK;
750 NS_IMETHODIMP
751 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
752 NS_IMETHODIMP
753 OnloadBlocker::Suspend(void) { return NS_OK; }
754 NS_IMETHODIMP
755 OnloadBlocker::Resume(void) { return NS_OK; }
757 NS_IMETHODIMP
758 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
759 *aLoadGroup = nullptr;
760 return NS_OK;
763 NS_IMETHODIMP
764 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
766 NS_IMETHODIMP
767 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
768 *aLoadFlags = nsIRequest::LOAD_NORMAL;
769 return NS_OK;
772 NS_IMETHODIMP
773 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
774 return GetTRRModeImpl(aTRRMode);
777 NS_IMETHODIMP
778 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
779 return SetTRRModeImpl(aTRRMode);
782 NS_IMETHODIMP
783 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
785 // ==================================================================
787 namespace dom {
789 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
791 Document* ExternalResourceMap::RequestResource(
792 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
793 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
794 // If we ever start allowing non-same-origin loads here, we might need to do
795 // something interesting with aRequestingPrincipal even for the hashtable
796 // gets.
797 MOZ_ASSERT(aURI, "Must have a URI");
798 MOZ_ASSERT(aRequestingNode, "Must have a node");
799 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
800 *aPendingLoad = nullptr;
801 if (mHaveShutDown) {
802 return nullptr;
805 // First, make sure we strip the ref from aURI.
806 nsCOMPtr<nsIURI> clone;
807 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
808 if (NS_FAILED(rv) || !clone) {
809 return nullptr;
812 ExternalResource* resource;
813 mMap.Get(clone, &resource);
814 if (resource) {
815 return resource->mDocument;
818 bool loadStartSucceeded =
819 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
820 if (!loadEntry) {
821 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
823 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
824 aRequestingNode))) {
825 return false;
829 RefPtr<PendingLoad> load(loadEntry.Data());
830 load.forget(aPendingLoad);
831 return true;
833 if (!loadStartSucceeded) {
834 // Make sure we don't thrash things by trying this load again, since
835 // chances are it failed for good reasons (security check, etc).
836 // This must be done outside the WithEntryHandle functor, as it accesses
837 // mPendingLoads.
838 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
841 return nullptr;
844 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
845 nsTArray<RefPtr<Document>> docs(mMap.Count());
846 for (const auto& entry : mMap.Values()) {
847 if (Document* doc = entry->mDocument) {
848 docs.AppendElement(doc);
852 for (auto& doc : docs) {
853 if (aCallback(*doc) == CallState::Stop) {
854 return;
859 void ExternalResourceMap::Traverse(
860 nsCycleCollectionTraversalCallback* aCallback) const {
861 // mPendingLoads will get cleared out as the requests complete, so
862 // no need to worry about those here.
863 for (const auto& entry : mMap) {
864 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
866 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
867 "mExternalResourceMap.mMap entry"
868 "->mDocument");
869 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
871 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
872 "mExternalResourceMap.mMap entry"
873 "->mViewer");
874 aCallback->NoteXPCOMChild(resource->mViewer);
876 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
877 "mExternalResourceMap.mMap entry"
878 "->mLoadGroup");
879 aCallback->NoteXPCOMChild(resource->mLoadGroup);
883 void ExternalResourceMap::HideViewers() {
884 for (const auto& entry : mMap) {
885 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
886 if (viewer) {
887 viewer->Hide();
892 void ExternalResourceMap::ShowViewers() {
893 for (const auto& entry : mMap) {
894 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
895 if (viewer) {
896 viewer->Show();
901 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
902 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
904 if (aFromDoc->IsShowing()) {
905 aToDoc->OnPageShow(true, nullptr);
909 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
910 nsIContentViewer* aViewer,
911 nsILoadGroup* aLoadGroup,
912 Document* aDisplayDocument) {
913 MOZ_ASSERT(aURI, "Unexpected call");
914 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
915 "Must have both or neither");
917 RefPtr<PendingLoad> load;
918 mPendingLoads.Remove(aURI, getter_AddRefs(load));
920 nsresult rv = NS_OK;
922 nsCOMPtr<Document> doc;
923 if (aViewer) {
924 doc = aViewer->GetDocument();
925 NS_ASSERTION(doc, "Must have a document");
927 doc->SetDisplayDocument(aDisplayDocument);
929 // Make sure that hiding our viewer will tear down its presentation.
930 aViewer->SetSticky(false);
932 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
933 if (NS_SUCCEEDED(rv)) {
934 rv = aViewer->Open(nullptr, nullptr);
937 if (NS_FAILED(rv)) {
938 doc = nullptr;
939 aViewer = nullptr;
940 aLoadGroup = nullptr;
944 ExternalResource* newResource =
945 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
947 newResource->mDocument = doc;
948 newResource->mViewer = aViewer;
949 newResource->mLoadGroup = aLoadGroup;
950 if (doc) {
951 if (nsPresContext* pc = doc->GetPresContext()) {
952 pc->RecomputeBrowsingContextDependentData();
954 TransferShowingState(aDisplayDocument, doc);
957 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
958 for (uint32_t i = 0; i < obs.Length(); ++i) {
959 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
960 nullptr);
963 return rv;
966 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
967 nsIRequestObserver)
969 NS_IMETHODIMP
970 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
971 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
972 if (map.HaveShutDown()) {
973 return NS_BINDING_ABORTED;
976 nsCOMPtr<nsIContentViewer> viewer;
977 nsCOMPtr<nsILoadGroup> loadGroup;
978 nsresult rv =
979 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
981 // Make sure to do this no matter what
982 nsresult rv2 =
983 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
984 if (NS_FAILED(rv)) {
985 return rv;
987 if (NS_FAILED(rv2)) {
988 mTargetListener = nullptr;
989 return rv2;
992 return mTargetListener->OnStartRequest(aRequest);
995 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
996 nsIRequest* aRequest, nsIContentViewer** aViewer,
997 nsILoadGroup** aLoadGroup) {
998 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
999 *aViewer = nullptr;
1000 *aLoadGroup = nullptr;
1002 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1003 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1005 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1006 if (httpChannel) {
1007 bool requestSucceeded;
1008 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1009 !requestSucceeded) {
1010 // Bail out on this load, since it looks like we have an HTTP error page
1011 return NS_BINDING_ABORTED;
1015 nsAutoCString type;
1016 chan->GetContentType(type);
1018 nsCOMPtr<nsILoadGroup> loadGroup;
1019 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1021 // Give this document its own loadgroup
1022 nsCOMPtr<nsILoadGroup> newLoadGroup =
1023 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1024 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1025 newLoadGroup->SetLoadGroup(loadGroup);
1027 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1028 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1030 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1031 new LoadgroupCallbacks(callbacks);
1032 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1034 // This is some serious hackery cribbed from docshell
1035 nsCOMPtr<nsICategoryManager> catMan =
1036 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
1037 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
1038 nsCString contractId;
1039 nsresult rv =
1040 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
1041 NS_ENSURE_SUCCESS(rv, rv);
1042 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1043 do_GetService(contractId.get());
1044 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1046 nsCOMPtr<nsIContentViewer> viewer;
1047 nsCOMPtr<nsIStreamListener> listener;
1048 rv = docLoaderFactory->CreateInstance(
1049 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1050 getter_AddRefs(listener), getter_AddRefs(viewer));
1051 NS_ENSURE_SUCCESS(rv, rv);
1052 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1054 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1055 if (!parser) {
1056 /// We don't want to deal with the various fake documents yet
1057 return NS_ERROR_NOT_IMPLEMENTED;
1060 // We can't handle HTML and other weird things here yet.
1061 nsIContentSink* sink = parser->GetContentSink();
1062 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1063 if (!xmlSink) {
1064 return NS_ERROR_NOT_IMPLEMENTED;
1067 listener.swap(mTargetListener);
1068 viewer.forget(aViewer);
1069 newLoadGroup.forget(aLoadGroup);
1070 return NS_OK;
1073 NS_IMETHODIMP
1074 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1075 nsIInputStream* aStream,
1076 uint64_t aOffset,
1077 uint32_t aCount) {
1078 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1079 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1080 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1081 return NS_BINDING_ABORTED;
1083 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1086 NS_IMETHODIMP
1087 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1088 nsresult aStatus) {
1089 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1090 if (mTargetListener) {
1091 nsCOMPtr<nsIStreamListener> listener;
1092 mTargetListener.swap(listener);
1093 return listener->OnStopRequest(aRequest, aStatus);
1096 return NS_OK;
1099 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1100 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1101 MOZ_ASSERT(aURI, "Must have a URI");
1102 MOZ_ASSERT(aRequestingNode, "Must have a node");
1103 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1105 nsCOMPtr<nsILoadGroup> loadGroup =
1106 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1108 nsresult rv = NS_OK;
1109 nsCOMPtr<nsIChannel> channel;
1110 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1111 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1112 nsIContentPolicy::TYPE_OTHER,
1113 nullptr, // aPerformanceStorage
1114 loadGroup);
1115 NS_ENSURE_SUCCESS(rv, rv);
1117 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1118 if (httpChannel) {
1119 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1120 Unused << NS_WARN_IF(NS_FAILED(rv));
1123 mURI = aURI;
1125 return channel->AsyncOpen(this);
1128 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1129 nsIInterfaceRequestor)
1131 #define IMPL_SHIM(_i) \
1132 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1134 IMPL_SHIM(nsILoadContext)
1135 IMPL_SHIM(nsIProgressEventSink)
1136 IMPL_SHIM(nsIChannelEventSink)
1138 #undef IMPL_SHIM
1140 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1142 #define TRY_SHIM(_i) \
1143 PR_BEGIN_MACRO \
1144 if (IID_IS(_i)) { \
1145 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1146 if (!real) { \
1147 return NS_NOINTERFACE; \
1149 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1150 shim.forget(aSink); \
1151 return NS_OK; \
1153 PR_END_MACRO
1155 NS_IMETHODIMP
1156 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1157 void** aSink) {
1158 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1159 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1160 return mCallbacks->GetInterface(aIID, aSink);
1163 *aSink = nullptr;
1165 TRY_SHIM(nsILoadContext);
1166 TRY_SHIM(nsIProgressEventSink);
1167 TRY_SHIM(nsIChannelEventSink);
1169 return NS_NOINTERFACE;
1172 #undef TRY_SHIM
1173 #undef IID_IS
1175 ExternalResourceMap::ExternalResource::~ExternalResource() {
1176 if (mViewer) {
1177 mViewer->Close(nullptr);
1178 mViewer->Destroy();
1182 // ==================================================================
1183 // =
1184 // ==================================================================
1186 // If we ever have an nsIDocumentObserver notification for stylesheet title
1187 // changes we should update the list from that instead of overriding
1188 // EnsureFresh.
1189 class DOMStyleSheetSetList final : public DOMStringList {
1190 public:
1191 explicit DOMStyleSheetSetList(Document* aDocument);
1193 void Disconnect() { mDocument = nullptr; }
1195 virtual void EnsureFresh() override;
1197 protected:
1198 Document* mDocument; // Our document; weak ref. It'll let us know if it
1199 // dies.
1202 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1203 : mDocument(aDocument) {
1204 NS_ASSERTION(mDocument, "Must have document!");
1207 void DOMStyleSheetSetList::EnsureFresh() {
1208 MOZ_ASSERT(NS_IsMainThread());
1210 mNames.Clear();
1212 if (!mDocument) {
1213 return; // Spec says "no exceptions", and we have no style sets if we have
1214 // no document, for sure
1217 size_t count = mDocument->SheetCount();
1218 nsAutoString title;
1219 for (size_t index = 0; index < count; index++) {
1220 StyleSheet* sheet = mDocument->SheetAt(index);
1221 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1222 sheet->GetTitle(title);
1223 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1224 return;
1229 // ==================================================================
1230 Document::SelectorCache::SelectorCache(nsIEventTarget* aEventTarget)
1231 : nsExpirationTracker<SelectorCacheKey, 4>(1000, "Document::SelectorCache",
1232 aEventTarget) {}
1234 Document::SelectorCache::~SelectorCache() { AgeAllGenerations(); }
1236 void Document::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector) {
1237 MOZ_ASSERT(NS_IsMainThread());
1238 MOZ_ASSERT(aSelector);
1240 // There is no guarantee that this method won't be re-entered when selector
1241 // matching is ongoing because "memory-pressure" could be notified immediately
1242 // when OOM happens according to the design of nsExpirationTracker.
1243 // The perfect solution is to delete the |aSelector| and its
1244 // RawServoSelectorList in mTable asynchronously.
1245 // We remove these objects synchronously for now because NotifyExpired() will
1246 // never be triggered by "memory-pressure" which is not implemented yet in
1247 // the stage 2 of mozalloc_handle_oom().
1248 // Once these objects are removed asynchronously, we should update the warning
1249 // added in mozalloc_handle_oom() as well.
1250 RemoveObject(aSelector);
1251 mTable.Remove(aSelector->mKey);
1252 delete aSelector;
1255 Document::FrameRequest::FrameRequest(FrameRequestCallback& aCallback,
1256 int32_t aHandle)
1257 : mCallback(&aCallback), mHandle(aHandle) {
1258 LogFrameRequestCallback::LogDispatch(mCallback);
1261 Document::FrameRequest::~FrameRequest() = default;
1263 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1265 struct Document::MetaViewportElementAndData {
1266 RefPtr<HTMLMetaElement> mElement;
1267 ViewportMetaData mData;
1269 bool operator==(const MetaViewportElementAndData& aOther) const {
1270 return mElement == aOther.mElement && mData == aOther.mData;
1274 // ==================================================================
1275 // =
1276 // ==================================================================
1278 Document::InternalCommandDataHashtable*
1279 Document::sInternalCommandDataHashtable = nullptr;
1281 // static
1282 void Document::Shutdown() {
1283 if (sInternalCommandDataHashtable) {
1284 sInternalCommandDataHashtable->Clear();
1285 delete sInternalCommandDataHashtable;
1286 sInternalCommandDataHashtable = nullptr;
1290 Document::Document(const char* aContentType)
1291 : nsINode(nullptr),
1292 DocumentOrShadowRoot(this),
1293 mCharacterSet(WINDOWS_1252_ENCODING),
1294 mCharacterSetSource(0),
1295 mParentDocument(nullptr),
1296 mCachedRootElement(nullptr),
1297 mNodeInfoManager(nullptr),
1298 #ifdef DEBUG
1299 mStyledLinksCleared(false),
1300 #endif
1301 mBlockAllMixedContent(false),
1302 mBlockAllMixedContentPreloads(false),
1303 mUpgradeInsecureRequests(false),
1304 mUpgradeInsecurePreloads(false),
1305 mDevToolsWatchingDOMMutations(false),
1306 mBidiEnabled(false),
1307 mMayNeedFontPrefsUpdate(true),
1308 mMathMLEnabled(false),
1309 mIsInitialDocumentInWindow(false),
1310 mIgnoreDocGroupMismatches(false),
1311 mLoadedAsData(false),
1312 mAddedToMemoryReportingAsDataDocument(false),
1313 mMayStartLayout(true),
1314 mHaveFiredTitleChange(false),
1315 mIsShowing(false),
1316 mVisible(true),
1317 mRemovedFromDocShell(false),
1318 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1319 // with various values that might disable it. Since we never prefetch
1320 // unless we get a window, and in that case the docshell value will get
1321 // &&-ed in, this is safe.
1322 mAllowDNSPrefetch(true),
1323 mIsStaticDocument(false),
1324 mCreatingStaticClone(false),
1325 mHasPrintCallbacks(false),
1326 mInUnlinkOrDeletion(false),
1327 mHasHadScriptHandlingObject(false),
1328 mIsBeingUsedAsImage(false),
1329 mDocURISchemeIsChrome(false),
1330 mInChromeDocShell(false),
1331 mIsDevToolsDocument(false),
1332 mIsSyntheticDocument(false),
1333 mHasLinksToUpdateRunnable(false),
1334 mFlushingPendingLinkUpdates(false),
1335 mMayHaveDOMMutationObservers(false),
1336 mMayHaveAnimationObservers(false),
1337 mHasCSP(false),
1338 mHasUnsafeEvalCSP(false),
1339 mHasUnsafeInlineCSP(false),
1340 mHasCSPDeliveredThroughHeader(false),
1341 mBFCacheDisallowed(false),
1342 mHasHadDefaultView(false),
1343 mStyleSheetChangeEventsEnabled(false),
1344 mShadowRootAttachedEventEnabled(false),
1345 mIsSrcdocDocument(false),
1346 mHasDisplayDocument(false),
1347 mFontFaceSetDirty(true),
1348 mDidFireDOMContentLoaded(true),
1349 mHasScrollLinkedEffect(false),
1350 mFrameRequestCallbacksScheduled(false),
1351 mIsTopLevelContentDocument(false),
1352 mIsContentDocument(false),
1353 mDidCallBeginLoad(false),
1354 mEncodingMenuDisabled(false),
1355 mLinksEnabled(true),
1356 mIsSVGGlyphsDocument(false),
1357 mInDestructor(false),
1358 mIsGoingAway(false),
1359 mInXBLUpdate(false),
1360 mNeedsReleaseAfterStackRefCntRelease(false),
1361 mStyleSetFilled(false),
1362 mQuirkSheetAdded(false),
1363 mContentEditableSheetAdded(false),
1364 mDesignModeSheetAdded(false),
1365 mSSApplicableStateNotificationPending(false),
1366 mMayHaveTitleElement(false),
1367 mDOMLoadingSet(false),
1368 mDOMInteractiveSet(false),
1369 mDOMCompleteSet(false),
1370 mAutoFocusFired(false),
1371 mScrolledToRefAlready(false),
1372 mChangeScrollPosWhenScrollingToRef(false),
1373 mDelayFrameLoaderInitialization(false),
1374 mSynchronousDOMContentLoaded(false),
1375 mMaybeServiceWorkerControlled(false),
1376 mAllowZoom(false),
1377 mValidScaleFloat(false),
1378 mValidMinScale(false),
1379 mValidMaxScale(false),
1380 mWidthStrEmpty(false),
1381 mParserAborted(false),
1382 mReportedDocumentUseCounters(false),
1383 mHasReportedShadowDOMUsage(false),
1384 mHasDelayedRefreshEvent(false),
1385 mLoadEventFiring(false),
1386 mSkipLoadEventAfterClose(false),
1387 mDisableCookieAccess(false),
1388 mDisableDocWrite(false),
1389 mTooDeepWriteRecursion(false),
1390 mPendingMaybeEditingStateChanged(false),
1391 mHasBeenEditable(false),
1392 mHasWarnedAboutZoom(false),
1393 mIsRunningExecCommand(false),
1394 mSetCompleteAfterDOMContentLoaded(false),
1395 mPendingFullscreenRequests(0),
1396 mXMLDeclarationBits(0),
1397 mOnloadBlockCount(0),
1398 mAsyncOnloadBlockCount(0),
1399 mWriteLevel(0),
1400 mLazyLoadImageCount(0),
1401 mLazyLoadImageStarted(0),
1402 mLazyLoadImageReachViewportLoading(0),
1403 mLazyLoadImageReachViewportLoaded(0),
1404 mContentEditableCount(0),
1405 mEditingState(EditingState::eOff),
1406 mCompatMode(eCompatibility_FullStandards),
1407 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1408 mAncestorIsLoading(false),
1409 mVisibilityState(dom::VisibilityState::Hidden),
1410 mType(eUnknown),
1411 mDefaultElementType(0),
1412 mAllowXULXBL(eTriUnset),
1413 mSkipDTDSecurityChecks(false),
1414 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1415 mSandboxFlags(0),
1416 mPartID(0),
1417 mMarkedCCGeneration(0),
1418 mPresShell(nullptr),
1419 mSubtreeModifiedDepth(0),
1420 mPreloadPictureDepth(0),
1421 mEventsSuppressed(0),
1422 mIgnoreDestructiveWritesCounter(0),
1423 mFrameRequestCallbackCounter(0),
1424 mStaticCloneCount(0),
1425 mWindow(nullptr),
1426 mBFCacheEntry(nullptr),
1427 mInSyncOperationCount(0),
1428 mBlockDOMContentLoaded(0),
1429 mUseCountersInitialized(false),
1430 mShouldReportUseCounters(false),
1431 mShouldSendPageUseCounters(false),
1432 mUserHasInteracted(false),
1433 mHasUserInteractionTimerScheduled(false),
1434 mStackRefCnt(0),
1435 mUpdateNestLevel(0),
1436 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1437 mViewportType(Unknown),
1438 mViewportFit(ViewportFitType::Auto),
1439 mSubDocuments(nullptr),
1440 mHeaderData(nullptr),
1441 mFlashClassification(FlashClassification::Unknown),
1442 mServoRestyleRootDirtyBits(0),
1443 mThrowOnDynamicMarkupInsertionCounter(0),
1444 mIgnoreOpensDuringUnloadCounter(0),
1445 mDocLWTheme(Doc_Theme_Uninitialized),
1446 mSavedResolution(1.0f),
1447 mSavedResolutionBeforeMVM(1.0f),
1448 mGeneration(0),
1449 mCachedTabSizeGeneration(0),
1450 mNextFormNumber(0),
1451 mNextControlNumber(0),
1452 mPreloadService(this),
1453 mShouldNotifyFetchSuccess(false),
1454 mShouldNotifyFormOrPasswordRemoved(false) {
1455 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1457 SetIsInDocument();
1458 SetIsConnected(true);
1460 // Create these unconditionally, they will be used to warn about the `zoom`
1461 // property, even if use counters are disabled.
1462 mStyleUseCounters = Servo_UseCounters_Create().Consume();
1464 SetContentTypeInternal(nsDependentCString(aContentType));
1466 // Start out mLastStyleSheetSet as null, per spec
1467 SetDOMStringToNull(mLastStyleSheetSet);
1469 // void state used to differentiate an empty source from an unselected source
1470 mPreloadPictureFoundSource.SetIsVoid(true);
1472 RecomputeLanguageFromCharset();
1474 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1475 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1478 #ifndef ANDROID
1479 // unused by GeckoView
1480 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1481 if (NS_WARN_IF(!aWin)) {
1482 return false;
1485 nsIURI* uri = aWin->GetDocumentURI();
1486 if (NS_WARN_IF(!uri)) {
1487 return false;
1489 // getSpec is an expensive operation, hence we first check the scheme
1490 // to see if the caller is actually an about: page.
1491 if (!uri->SchemeIs("about")) {
1492 return false;
1495 nsAutoCString aboutSpec;
1496 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1497 NS_ENSURE_SUCCESS(rv, false);
1499 return aboutSpec.EqualsASCII(aSpec);
1501 #endif
1503 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1504 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1505 #ifdef ANDROID
1506 // GeckoView uses data URLs for error pages, so for now just check for any
1507 // error page
1508 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1509 #else
1510 return win && IsAboutErrorPage(win, "neterror");
1511 #endif
1514 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1515 bool aIsTemporary) {
1516 nsIGlobalObject* global = GetScopeObject();
1517 if (!global) {
1518 return nullptr;
1521 ErrorResult er;
1522 RefPtr<Promise> promise =
1523 Promise::Create(global, er, Promise::ePropagateUserInteraction);
1524 if (er.Failed()) {
1525 return nullptr;
1528 nsCOMPtr<nsISupports> info;
1529 nsCOMPtr<nsITransportSecurityInfo> tsi;
1530 nsresult rv = NS_OK;
1531 if (NS_WARN_IF(!mFailedChannel)) {
1532 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1533 return promise.forget();
1536 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1537 if (NS_WARN_IF(NS_FAILED(rv))) {
1538 promise->MaybeReject(rv);
1539 return promise.forget();
1541 nsCOMPtr<nsIURI> failedChannelURI;
1542 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1543 if (!failedChannelURI) {
1544 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1545 return promise.forget();
1548 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1549 if (!innerURI) {
1550 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1551 return promise.forget();
1554 nsAutoCString host;
1555 innerURI->GetAsciiHost(host);
1556 int32_t port;
1557 innerURI->GetPort(&port);
1559 tsi = do_QueryInterface(info);
1560 if (NS_WARN_IF(!tsi)) {
1561 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1562 return promise.forget();
1565 bool isUntrusted = true;
1566 rv = tsi->GetIsUntrusted(&isUntrusted);
1567 if (NS_WARN_IF(NS_FAILED(rv))) {
1568 promise->MaybeReject(rv);
1569 return promise.forget();
1572 bool isDomainMismatch = true;
1573 rv = tsi->GetIsDomainMismatch(&isDomainMismatch);
1574 if (NS_WARN_IF(NS_FAILED(rv))) {
1575 promise->MaybeReject(rv);
1576 return promise.forget();
1579 bool isNotValidAtThisTime = true;
1580 rv = tsi->GetIsNotValidAtThisTime(&isNotValidAtThisTime);
1581 if (NS_WARN_IF(NS_FAILED(rv))) {
1582 promise->MaybeReject(rv);
1583 return promise.forget();
1586 nsCOMPtr<nsIX509Cert> cert;
1587 rv = tsi->GetServerCert(getter_AddRefs(cert));
1588 if (NS_WARN_IF(NS_FAILED(rv))) {
1589 promise->MaybeReject(rv);
1590 return promise.forget();
1592 if (NS_WARN_IF(!cert)) {
1593 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1594 return promise.forget();
1597 uint32_t flags = 0;
1598 if (isUntrusted) {
1599 flags |= nsICertOverrideService::ERROR_UNTRUSTED;
1601 if (isDomainMismatch) {
1602 flags |= nsICertOverrideService::ERROR_MISMATCH;
1604 if (isNotValidAtThisTime) {
1605 flags |= nsICertOverrideService::ERROR_TIME;
1608 if (XRE_IsContentProcess()) {
1609 nsCOMPtr<nsISerializable> certSer = do_QueryInterface(cert);
1610 nsCString certSerialized;
1611 NS_SerializeToString(certSer, certSerialized);
1613 ContentChild* cc = ContentChild::GetSingleton();
1614 MOZ_ASSERT(cc);
1615 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1616 cc->SendAddCertException(certSerialized, flags, host, port, attrs,
1617 aIsTemporary)
1618 ->Then(GetCurrentSerialEventTarget(), __func__,
1619 [promise](const mozilla::MozPromise<
1620 nsresult, mozilla::ipc::ResponseRejectReason,
1621 true>::ResolveOrRejectValue& aValue) {
1622 if (aValue.IsResolve()) {
1623 promise->MaybeResolve(aValue.ResolveValue());
1624 } else {
1625 promise->MaybeRejectWithUndefined();
1628 return promise.forget();
1631 if (XRE_IsParentProcess()) {
1632 nsCOMPtr<nsICertOverrideService> overrideService =
1633 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1634 if (!overrideService) {
1635 promise->MaybeReject(NS_ERROR_FAILURE);
1636 return promise.forget();
1639 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1640 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1641 flags, aIsTemporary);
1642 if (NS_WARN_IF(NS_FAILED(rv))) {
1643 promise->MaybeReject(rv);
1644 return promise.forget();
1647 promise->MaybeResolveWithUndefined();
1648 return promise.forget();
1651 promise->MaybeReject(NS_ERROR_FAILURE);
1652 return promise.forget();
1655 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1656 nsCOMPtr<nsISupports> info;
1657 nsCOMPtr<nsITransportSecurityInfo> tsi;
1658 nsresult rv = NS_OK;
1659 if (NS_WARN_IF(!mFailedChannel)) {
1660 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1661 return;
1664 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1665 if (NS_WARN_IF(NS_FAILED(rv))) {
1666 aRv.Throw(rv);
1667 return;
1669 tsi = do_QueryInterface(info);
1670 if (NS_WARN_IF(!tsi)) {
1671 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1672 return;
1675 nsAutoString errorCodeString;
1676 rv = tsi->GetErrorCodeString(errorCodeString);
1677 if (NS_WARN_IF(NS_FAILED(rv))) {
1678 aRv.Throw(rv);
1679 return;
1681 aInfo.mErrorCodeString.Assign(errorCodeString);
1684 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1685 JSObject* aObject) {
1686 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1687 #ifdef ANDROID
1688 // GeckoView uses data URLs for error pages, so for now just check for any
1689 // error page
1690 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1691 #else
1692 return win && IsAboutErrorPage(win, "certerror");
1693 #endif
1696 bool Document::IsErrorPage() const {
1697 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1698 return loadInfo && loadInfo->GetLoadErrorPage();
1701 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1702 ErrorResult& aRv) {
1703 nsCOMPtr<nsISupports> info;
1704 nsCOMPtr<nsITransportSecurityInfo> tsi;
1705 nsresult rv = NS_OK;
1706 if (NS_WARN_IF(!mFailedChannel)) {
1707 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1708 return;
1711 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1712 if (NS_WARN_IF(NS_FAILED(rv))) {
1713 aRv.Throw(rv);
1714 return;
1716 tsi = do_QueryInterface(info);
1717 if (NS_WARN_IF(!tsi)) {
1718 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1719 return;
1722 nsAutoString errorCodeString;
1723 rv = tsi->GetErrorCodeString(errorCodeString);
1724 if (NS_WARN_IF(NS_FAILED(rv))) {
1725 aRv.Throw(rv);
1726 return;
1728 aInfo.mErrorCodeString.Assign(errorCodeString);
1730 rv = tsi->GetIsUntrusted(&aInfo.mIsUntrusted);
1731 if (NS_WARN_IF(NS_FAILED(rv))) {
1732 aRv.Throw(rv);
1733 return;
1736 rv = tsi->GetIsDomainMismatch(&aInfo.mIsDomainMismatch);
1737 if (NS_WARN_IF(NS_FAILED(rv))) {
1738 aRv.Throw(rv);
1739 return;
1742 rv = tsi->GetIsNotValidAtThisTime(&aInfo.mIsNotValidAtThisTime);
1743 if (NS_WARN_IF(NS_FAILED(rv))) {
1744 aRv.Throw(rv);
1745 return;
1748 nsCOMPtr<nsIX509Cert> cert;
1749 nsCOMPtr<nsIX509CertValidity> validity;
1750 rv = tsi->GetServerCert(getter_AddRefs(cert));
1751 if (NS_WARN_IF(NS_FAILED(rv))) {
1752 aRv.Throw(rv);
1753 return;
1755 if (NS_WARN_IF(!cert)) {
1756 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1757 return;
1760 rv = cert->GetValidity(getter_AddRefs(validity));
1761 if (NS_WARN_IF(NS_FAILED(rv))) {
1762 aRv.Throw(rv);
1763 return;
1765 if (NS_WARN_IF(!validity)) {
1766 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1767 return;
1770 PRTime validityResult;
1771 rv = validity->GetNotBefore(&validityResult);
1772 if (NS_WARN_IF(NS_FAILED(rv))) {
1773 aRv.Throw(rv);
1774 return;
1776 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1778 rv = validity->GetNotAfter(&validityResult);
1779 if (NS_WARN_IF(NS_FAILED(rv))) {
1780 aRv.Throw(rv);
1781 return;
1783 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1785 nsAutoString issuerCommonName;
1786 nsAutoString certChainPEMString;
1787 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1788 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1789 int64_t minValidity = 0;
1790 PRTime notBefore, notAfter;
1791 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1792 rv = tsi->GetFailedCertChain(failedCertArray);
1793 if (NS_WARN_IF(NS_FAILED(rv))) {
1794 aRv.Throw(rv);
1795 return;
1798 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1799 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1800 return;
1803 for (const auto& certificate : failedCertArray) {
1804 rv = certificate->GetIssuerCommonName(issuerCommonName);
1805 if (NS_WARN_IF(NS_FAILED(rv))) {
1806 aRv.Throw(rv);
1807 return;
1810 rv = certificate->GetValidity(getter_AddRefs(validity));
1811 if (NS_WARN_IF(NS_FAILED(rv))) {
1812 aRv.Throw(rv);
1813 return;
1815 if (NS_WARN_IF(!validity)) {
1816 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1817 return;
1820 rv = validity->GetNotBefore(&notBefore);
1821 if (NS_WARN_IF(NS_FAILED(rv))) {
1822 aRv.Throw(rv);
1823 return;
1826 rv = validity->GetNotAfter(&notAfter);
1827 if (NS_WARN_IF(NS_FAILED(rv))) {
1828 aRv.Throw(rv);
1829 return;
1832 notBefore = std::max(minValidity, notBefore);
1833 notAfter = std::min(maxValidity, notAfter);
1834 nsTArray<uint8_t> certArray;
1835 rv = certificate->GetRawDER(certArray);
1836 if (NS_WARN_IF(NS_FAILED(rv))) {
1837 aRv.Throw(rv);
1838 return;
1841 nsAutoString der64;
1842 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1843 certArray.Length(), der64);
1844 if (NS_WARN_IF(NS_FAILED(rv))) {
1845 aRv.Throw(rv);
1846 return;
1848 if (!certChainStrings.AppendElement(der64, fallible)) {
1849 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1850 return;
1854 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1855 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1856 aInfo.mCertValidityRangeNotBefore =
1857 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1859 int32_t errorCode;
1860 rv = tsi->GetErrorCode(&errorCode);
1861 if (NS_WARN_IF(NS_FAILED(rv))) {
1862 aRv.Throw(rv);
1863 return;
1866 nsCOMPtr<nsINSSErrorsService> nsserr =
1867 do_GetService("@mozilla.org/nss_errors_service;1");
1868 if (NS_WARN_IF(!nsserr)) {
1869 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1870 return;
1872 nsresult res;
1873 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1874 if (NS_WARN_IF(NS_FAILED(rv))) {
1875 aRv.Throw(rv);
1876 return;
1878 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1879 if (NS_WARN_IF(NS_FAILED(rv))) {
1880 aRv.Throw(rv);
1881 return;
1884 bool isPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(this);
1885 uint32_t flags =
1886 isPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
1887 OriginAttributes attrs;
1888 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1889 nsCOMPtr<nsIURI> aURI;
1890 mFailedChannel->GetURI(getter_AddRefs(aURI));
1891 if (XRE_IsContentProcess()) {
1892 ContentChild* cc = ContentChild::GetSingleton();
1893 MOZ_ASSERT(cc);
1894 cc->SendIsSecureURI(aURI, flags, attrs, &aInfo.mHasHSTS);
1895 } else {
1896 nsCOMPtr<nsISiteSecurityService> sss =
1897 do_GetService(NS_SSSERVICE_CONTRACTID);
1898 if (NS_WARN_IF(!sss)) {
1899 return;
1901 Unused << NS_WARN_IF(NS_FAILED(sss->IsSecureURI(aURI, flags, attrs, nullptr,
1902 nullptr, &aInfo.mHasHSTS)));
1904 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1905 do_GetService(NS_PKPSERVICE_CONTRACTID);
1906 if (NS_WARN_IF(!pkps)) {
1907 return;
1909 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1912 bool Document::AllowDeprecatedTls() {
1913 return Preferences::GetBool("security.tls.version.enable-deprecated", false);
1916 void Document::SetAllowDeprecatedTls(bool value) {
1917 if (!IsErrorPage()) {
1918 return;
1921 auto docShell = GetDocShell();
1922 if (!docShell) {
1923 return;
1926 auto child = BrowserChild::GetFrom(docShell);
1927 if (!child) {
1928 return;
1931 child->SendSetAllowDeprecatedTls(value);
1934 bool Document::IsAboutPage() const {
1935 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
1936 return principal->SchemeIs("about");
1939 void Document::ConstructUbiNode(void* storage) {
1940 JS::ubi::Concrete<Document>::construct(storage, this);
1943 void Document::LoadEventFired() {
1944 // Accumulate timing data located in each document's realm and report to
1945 // telemetry.
1946 AccumulateJSTelemetry();
1948 // Collect page load timings
1949 AccumulatePageLoadTelemetry();
1951 // Release the JS bytecode cache from its wait on the load event, and
1952 // potentially dispatch the encoding of the bytecode.
1953 if (ScriptLoader()) {
1954 ScriptLoader()->LoadEventFired();
1958 void Document::AccumulatePageLoadTelemetry() {
1959 // Interested only in top level documents for real websites that are in the
1960 // foreground.
1961 if (!ShouldIncludeInTelemetry(false) || !IsTopLevelContentDocument() ||
1962 !GetNavigationTiming() ||
1963 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
1964 return;
1967 if (!GetChannel()) {
1968 return;
1971 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
1972 if (!timedChannel) {
1973 return;
1976 TimeStamp responseStart;
1977 timedChannel->GetResponseStart(&responseStart);
1979 TimeStamp navigationStart =
1980 GetNavigationTiming()->GetNavigationStartTimeStamp();
1982 if (!responseStart || !navigationStart) {
1983 return;
1986 nsCString http3Key;
1987 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
1988 do_QueryInterface(GetChannel());
1989 if (httpChannel) {
1990 uint32_t major;
1991 uint32_t minor;
1992 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
1993 if (major == 3) {
1994 http3Key = "http3"_ns;
1995 } else if (major == 2) {
1996 bool supportHttp3 = false;
1997 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
1998 supportHttp3 = false;
2000 if (supportHttp3) {
2001 http3Key = "supports_http3"_ns;
2007 // First Contentful Paint
2008 if (TimeStamp firstContentfulPaint =
2009 GetNavigationTiming()->GetFirstContentfulPaintTimeStamp()) {
2010 Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS,
2011 navigationStart, firstContentfulPaint);
2013 if (!http3Key.IsEmpty()) {
2014 Telemetry::AccumulateTimeDelta(
2015 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2016 navigationStart, firstContentfulPaint);
2019 Telemetry::AccumulateTimeDelta(
2020 Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS,
2021 responseStart, firstContentfulPaint);
2024 // DOM Content Loaded event
2025 if (TimeStamp dclEventStart =
2026 GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) {
2027 Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS,
2028 navigationStart, dclEventStart);
2029 Telemetry::AccumulateTimeDelta(
2030 Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS,
2031 responseStart, dclEventStart);
2034 // Load event
2035 if (TimeStamp loadEventStart =
2036 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2037 Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS,
2038 navigationStart, loadEventStart);
2039 if (!http3Key.IsEmpty()) {
2040 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2041 http3Key, navigationStart, loadEventStart);
2044 Telemetry::AccumulateTimeDelta(
2045 Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
2046 loadEventStart);
2050 void Document::AccumulateJSTelemetry() {
2051 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry(false)) {
2052 return;
2055 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2056 return;
2059 AutoJSContext cx;
2060 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2061 JSAutoRealm ar(cx, globalObject);
2062 JS::JSTimers timers = JS::GetJSTimers(cx);
2064 if (!timers.executionTime.IsZero()) {
2065 Telemetry::Accumulate(
2066 Telemetry::JS_PAGELOAD_EXECUTION_MS,
2067 static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
2070 if (!timers.delazificationTime.IsZero()) {
2071 Telemetry::Accumulate(
2072 Telemetry::JS_PAGELOAD_DELAZIFICATION_MS,
2073 static_cast<uint32_t>(timers.delazificationTime.ToMilliseconds()));
2076 if (!timers.xdrEncodingTime.IsZero()) {
2077 Telemetry::Accumulate(
2078 Telemetry::JS_PAGELOAD_XDR_ENCODING_MS,
2079 static_cast<uint32_t>(timers.xdrEncodingTime.ToMilliseconds()));
2082 if (!timers.baselineCompileTime.IsZero()) {
2083 Telemetry::Accumulate(
2084 Telemetry::JS_PAGELOAD_BASELINE_COMPILE_MS,
2085 static_cast<uint32_t>(timers.baselineCompileTime.ToMilliseconds()));
2088 if (!timers.gcTime.IsZero()) {
2089 Telemetry::Accumulate(
2090 Telemetry::JS_PAGELOAD_GC_MS,
2091 static_cast<uint32_t>(timers.gcTime.ToMilliseconds()));
2094 if (!timers.protectTime.IsZero()) {
2095 Telemetry::Accumulate(
2096 Telemetry::JS_PAGELOAD_PROTECT_MS,
2097 static_cast<uint32_t>(timers.protectTime.ToMilliseconds()));
2101 Document::~Document() {
2102 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2103 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2104 "Can't be top-level and a resource doc at the same time");
2106 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2108 if (IsTopLevelContentDocument()) {
2109 RemoveToplevelLoadingDocument(this);
2111 // don't report for about: pages
2112 if (!IsAboutPage()) {
2113 // record CSP telemetry on this document
2114 if (mHasCSP) {
2115 Accumulate(Telemetry::CSP_DOCUMENTS_COUNT, 1);
2117 if (mHasUnsafeInlineCSP) {
2118 Accumulate(Telemetry::CSP_UNSAFE_INLINE_DOCUMENTS_COUNT, 1);
2120 if (mHasUnsafeEvalCSP) {
2121 Accumulate(Telemetry::CSP_UNSAFE_EVAL_DOCUMENTS_COUNT, 1);
2124 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2125 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
2128 if (IsHTMLDocument()) {
2129 switch (GetCompatibilityMode()) {
2130 case eCompatibility_FullStandards:
2131 Telemetry::AccumulateCategorical(
2132 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
2133 break;
2134 case eCompatibility_AlmostStandards:
2135 Telemetry::AccumulateCategorical(
2136 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
2137 break;
2138 case eCompatibility_NavQuirks:
2139 Telemetry::AccumulateCategorical(
2140 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
2141 break;
2142 default:
2143 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
2144 break;
2150 mInDestructor = true;
2151 mInUnlinkOrDeletion = true;
2153 mozilla::DropJSObjects(this);
2155 // Clear mObservers to keep it in sync with the mutationobserver list
2156 mObservers.Clear();
2158 mIntersectionObservers.Clear();
2160 if (mStyleSheetSetList) {
2161 mStyleSheetSetList->Disconnect();
2164 if (mAnimationController) {
2165 mAnimationController->Disconnect();
2168 MOZ_ASSERT(mTimelines.isEmpty());
2170 mParentDocument = nullptr;
2172 // Kill the subdocument map, doing this will release its strong
2173 // references, if any.
2174 delete mSubDocuments;
2175 mSubDocuments = nullptr;
2177 nsAutoScriptBlocker scriptBlocker;
2179 // Destroy link map now so we don't waste time removing
2180 // links one by one
2181 DestroyElementMaps();
2183 // Invalidate cached array of child nodes
2184 InvalidateChildNodes();
2186 // We should not have child nodes when destructor is called,
2187 // since child nodes keep their owner document alive.
2188 MOZ_ASSERT(!HasChildren());
2190 mCachedRootElement = nullptr;
2192 for (auto& sheets : mAdditionalSheets) {
2193 UnlinkStyleSheets(sheets);
2196 if (mAttrStyleSheet) {
2197 mAttrStyleSheet->SetOwningDocument(nullptr);
2200 if (mListenerManager) {
2201 mListenerManager->Disconnect();
2202 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2205 if (mScriptLoader) {
2206 mScriptLoader->DropDocumentReference();
2209 if (mCSSLoader) {
2210 // Could be null here if Init() failed or if we have been unlinked.
2211 mCSSLoader->DropDocumentReference();
2214 if (mStyleImageLoader) {
2215 mStyleImageLoader->DropDocumentReference();
2218 if (mXULBroadcastManager) {
2219 mXULBroadcastManager->DropDocumentReference();
2222 if (mXULPersist) {
2223 mXULPersist->DropDocumentReference();
2226 if (mPermissionDelegateHandler) {
2227 mPermissionDelegateHandler->DropDocumentReference();
2230 delete mHeaderData;
2232 mPendingTitleChangeEvent.Revoke();
2234 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2235 "must not have media query lists left");
2237 if (mNodeInfoManager) {
2238 mNodeInfoManager->DropDocumentReference();
2241 if (mDocGroup) {
2242 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2243 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2246 UnlinkOriginalDocumentIfStatic();
2248 UnregisterFromMemoryReportingForDataDocument();
2251 NS_INTERFACE_TABLE_HEAD(Document)
2252 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2253 NS_INTERFACE_TABLE_BEGIN
2254 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2255 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2256 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2257 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2258 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2259 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2260 NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
2261 NS_INTERFACE_TABLE_END
2262 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2263 NS_INTERFACE_MAP_END
2265 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2266 NS_IMETHODIMP_(MozExternalRefCountType)
2267 Document::Release() {
2268 MOZ_ASSERT(0 != mRefCnt, "dup release");
2269 NS_ASSERT_OWNINGTHREAD(Document);
2270 nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(Document)::Upcast(this);
2271 bool shouldDelete = false;
2272 nsrefcnt count = mRefCnt.decr(base, &shouldDelete);
2273 NS_LOG_RELEASE(this, count, "Document");
2274 if (count == 0) {
2275 if (mStackRefCnt && !mNeedsReleaseAfterStackRefCntRelease) {
2276 mNeedsReleaseAfterStackRefCntRelease = true;
2277 NS_ADDREF_THIS();
2278 return mRefCnt.get();
2280 mRefCnt.incr(base);
2281 LastRelease();
2282 mRefCnt.decr(base);
2283 if (shouldDelete) {
2284 mRefCnt.stabilizeForDeletion();
2285 DeleteCycleCollectable();
2288 return count;
2291 NS_IMETHODIMP_(void)
2292 Document::DeleteCycleCollectable() { delete this; }
2294 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2295 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2296 EventListenerManager* elm = tmp->GetExistingListenerManager();
2297 if (elm) {
2298 elm->MarkForCC();
2300 return true;
2302 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2304 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2305 return Element::CanSkipInCC(tmp);
2306 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2308 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2309 return Element::CanSkipThis(tmp);
2310 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2312 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2313 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2314 char name[512];
2315 nsAutoCString loadedAsData;
2316 if (tmp->IsLoadedAsData()) {
2317 loadedAsData.AssignLiteral("data");
2318 } else {
2319 loadedAsData.AssignLiteral("normal");
2321 uint32_t nsid = tmp->GetDefaultNamespaceID();
2322 nsAutoCString uri;
2323 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2324 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2325 "(xhtml)", "(XLink)", "(XSLT)",
2326 "(MathML)", "(RDF)", "(XUL)"};
2327 if (nsid < ArrayLength(kNSURIs)) {
2328 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2329 kNSURIs[nsid], uri.get());
2330 } else {
2331 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2333 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2334 } else {
2335 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2338 if (!nsINode::Traverse(tmp, cb)) {
2339 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2342 tmp->mExternalResourceMap.Traverse(&cb);
2344 // Traverse all Document pointer members.
2345 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2346 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2347 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2348 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2349 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2351 // Traverse all Document nsCOMPtrs.
2352 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2353 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2354 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2355 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2356 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2358 DocumentOrShadowRoot::Traverse(tmp, cb);
2360 for (auto& sheets : tmp->mAdditionalSheets) {
2361 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2364 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2365 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver)
2366 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserverViewport)
2367 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2368 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2369 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2370 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2371 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2372 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached)
2373 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2374 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
2375 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2376 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2377 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2378 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2379 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2380 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2381 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2382 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2383 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2384 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2385 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2386 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2387 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2388 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2389 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2390 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2391 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2393 // Traverse all our nsCOMArrays.
2394 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2396 for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) {
2397 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]");
2398 cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback);
2401 // Traverse animation components
2402 if (tmp->mAnimationController) {
2403 tmp->mAnimationController->Traverse(&cb);
2406 if (tmp->mSubDocuments) {
2407 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2408 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2410 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2411 cb.NoteXPCOMChild(entry->mKey);
2412 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2413 "mSubDocuments entry->mSubDocument");
2414 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2418 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2420 // We own only the items in mDOMMediaQueryLists that have listeners;
2421 // this reference is managed by their AddListener and RemoveListener
2422 // methods.
2423 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2424 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2425 if (mql->HasListeners() &&
2426 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2427 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2428 cb.NoteXPCOMChild(mql);
2432 for (size_t i = 0; i < tmp->mMetaViewports.Length(); i++) {
2433 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMetaViewports[i].mElement);
2436 // XXX: This should be not needed once bug 1569185 lands.
2437 for (const auto& entry : tmp->mL10nProtoElements) {
2438 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2439 cb.NoteXPCOMChild(entry.GetKey());
2440 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2443 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2444 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2445 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2446 mPendingFrameStaticClones[i].mStaticCloneOf);
2448 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2450 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2452 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Document)
2454 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2455 tmp->mInUnlinkOrDeletion = true;
2457 // Clear out our external resources
2458 tmp->mExternalResourceMap.Shutdown();
2460 nsAutoScriptBlocker scriptBlocker;
2462 nsINode::Unlink(tmp);
2464 while (tmp->HasChildren()) {
2465 // Hold a strong ref to the node when we remove it, because we may be
2466 // the last reference to it.
2467 // If this code changes, change the corresponding code in Document's
2468 // unlink impl and ContentUnbinder::UnbindSubtree.
2469 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2470 tmp->DisconnectChild(child);
2471 child->UnbindFromTree();
2474 tmp->UnlinkOriginalDocumentIfStatic();
2476 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2478 tmp->SetScriptGlobalObject(nullptr);
2480 for (auto& sheets : tmp->mAdditionalSheets) {
2481 tmp->UnlinkStyleSheets(sheets);
2484 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2485 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2486 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver)
2487 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserverViewport)
2488 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2489 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2490 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2491 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2492 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2493 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2494 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2495 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2496 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2497 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2498 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStateObjectCached)
2499 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2500 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
2501 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2502 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2503 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2504 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2505 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2506 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2507 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2508 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2509 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2510 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2511 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2512 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2513 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2514 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2515 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2516 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2517 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2518 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2520 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2521 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2522 tmp->mDocGroup);
2524 tmp->mDocGroup = nullptr;
2526 if (tmp->IsTopLevelContentDocument()) {
2527 RemoveToplevelLoadingDocument(tmp);
2530 tmp->mParentDocument = nullptr;
2532 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2534 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2536 if (tmp->mListenerManager) {
2537 tmp->mListenerManager->Disconnect();
2538 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2539 tmp->mListenerManager = nullptr;
2542 if (tmp->mStyleSheetSetList) {
2543 tmp->mStyleSheetSetList->Disconnect();
2544 tmp->mStyleSheetSetList = nullptr;
2547 delete tmp->mSubDocuments;
2548 tmp->mSubDocuments = nullptr;
2550 tmp->mFrameRequestCallbacks.Clear();
2551 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2552 "How did we get here without our presshell going away "
2553 "first?");
2555 DocumentOrShadowRoot::Unlink(tmp);
2557 // Document has a pretty complex destructor, so we're going to
2558 // assume that *most* cycles you actually want to break somewhere
2559 // else, and not unlink an awful lot here.
2561 tmp->mExpandoAndGeneration.OwnerUnlinked();
2563 if (tmp->mAnimationController) {
2564 tmp->mAnimationController->Unlink();
2567 tmp->mPendingTitleChangeEvent.Revoke();
2569 if (tmp->mCSSLoader) {
2570 tmp->mCSSLoader->DropDocumentReference();
2571 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2574 // We own only the items in mDOMMediaQueryLists that have listeners;
2575 // this reference is managed by their AddListener and RemoveListener
2576 // methods.
2577 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2578 MediaQueryList* next =
2579 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2580 mql->Disconnect();
2581 mql = next;
2584 tmp->mPendingFrameStaticClones.Clear();
2586 tmp->mInUnlinkOrDeletion = false;
2588 tmp->mMetaViewports.Clear();
2590 tmp->UnregisterFromMemoryReportingForDataDocument();
2592 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2593 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2594 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2595 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2597 nsresult Document::Init() {
2598 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2599 return NS_ERROR_ALREADY_INITIALIZED;
2602 // Force initialization.
2603 mOnloadBlocker = new OnloadBlocker();
2604 mStyleImageLoader = new css::ImageLoader(this);
2606 mNodeInfoManager = new nsNodeInfoManager();
2607 nsresult rv = mNodeInfoManager->Init(this);
2608 NS_ENSURE_SUCCESS(rv, rv);
2610 // mNodeInfo keeps NodeInfoManager alive!
2611 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2612 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2613 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2614 "Bad NodeType in aNodeInfo");
2616 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2618 mCSSLoader = new css::Loader(this);
2619 // Assume we're not quirky, until we know otherwise
2620 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2622 // If after creation the owner js global is not set for a document
2623 // we use the default compartment for this document, instead of creating
2624 // wrapper in some random compartment when the document is exposed to js
2625 // via some events.
2626 nsCOMPtr<nsIGlobalObject> global =
2627 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2628 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2629 mScopeObject = do_GetWeakReference(global);
2630 MOZ_ASSERT(mScopeObject);
2632 mScriptLoader = new dom::ScriptLoader(this);
2634 // we need to create a policy here so getting the policy within
2635 // ::Policy() can *always* return a non null policy
2636 mFeaturePolicy = new dom::FeaturePolicy(this);
2637 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2639 mStyleSet = MakeUnique<ServoStyleSet>(*this);
2641 mozilla::HoldJSObjects(this);
2643 return NS_OK;
2646 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2648 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2649 PropertyTable().RemoveAllPropertiesFor(aNode);
2652 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2653 nsCOMPtr<nsIURI> uri;
2654 nsCOMPtr<nsIPrincipal> principal;
2655 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2656 if (aChannel) {
2657 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2658 // nsScriptSecurityManager::GetChannelResultPrincipals.
2659 // Note: this should match the uri used for the OnNewURI call in
2660 // nsDocShell::CreateContentViewer.
2661 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2663 nsIScriptSecurityManager* securityManager =
2664 nsContentUtils::GetSecurityManager();
2665 if (securityManager) {
2666 securityManager->GetChannelResultPrincipals(
2667 aChannel, getter_AddRefs(principal),
2668 getter_AddRefs(partitionedPrincipal));
2672 bool equal = principal->Equals(partitionedPrincipal);
2674 principal = MaybeDowngradePrincipal(principal);
2675 if (equal) {
2676 partitionedPrincipal = principal;
2677 } else {
2678 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2681 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2683 // Note that, since mTiming does not change during a reset, the
2684 // navigationStart time remains unchanged and therefore any future new
2685 // timeline will have the same global clock time as the old one.
2686 mDocumentTimeline = nullptr;
2688 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2689 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2690 mDocumentBaseURI = baseURI.forget();
2691 mChromeXHRDocBaseURI = nullptr;
2695 mChannel = aChannel;
2698 void Document::DisconnectNodeTree() {
2699 // Delete references to sub-documents and kill the subdocument map,
2700 // if any. This is not strictly needed, but makes the node tree
2701 // teardown a bit faster.
2702 delete mSubDocuments;
2703 mSubDocuments = nullptr;
2705 bool oldVal = mInUnlinkOrDeletion;
2706 mInUnlinkOrDeletion = true;
2707 { // Scope for update
2708 MOZ_AUTO_DOC_UPDATE(this, true);
2710 // Destroy link map now so we don't waste time removing
2711 // links one by one
2712 DestroyElementMaps();
2714 // Invalidate cached array of child nodes
2715 InvalidateChildNodes();
2717 while (HasChildren()) {
2718 nsMutationGuard::DidMutate();
2719 nsCOMPtr<nsIContent> content = GetLastChild();
2720 nsIContent* previousSibling = content->GetPreviousSibling();
2721 DisconnectChild(content);
2722 if (content == mCachedRootElement) {
2723 // Immediately clear mCachedRootElement, now that it's been removed
2724 // from mChildren, so that GetRootElement() will stop returning this
2725 // now-stale value.
2726 mCachedRootElement = nullptr;
2728 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2729 content->UnbindFromTree();
2731 MOZ_ASSERT(!mCachedRootElement,
2732 "After removing all children, there should be no root elem");
2734 mInUnlinkOrDeletion = oldVal;
2737 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2738 nsIPrincipal* aPrincipal,
2739 nsIPrincipal* aPartitionedPrincipal) {
2740 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2741 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2743 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2744 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2746 mSecurityInfo = nullptr;
2748 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2749 if (!aLoadGroup || group != aLoadGroup) {
2750 mDocumentLoadGroup = nullptr;
2753 DisconnectNodeTree();
2755 // Reset our stylesheets
2756 ResetStylesheetsToURI(aURI);
2758 // Release the listener manager
2759 if (mListenerManager) {
2760 mListenerManager->Disconnect();
2761 mListenerManager = nullptr;
2764 // Release the stylesheets list.
2765 mDOMStyleSheets = nullptr;
2767 // Release our principal after tearing down the document, rather than before.
2768 // This ensures that, during teardown, the document and the dying window
2769 // (which already nulled out its document pointer and cached the principal)
2770 // have matching principals.
2771 SetPrincipals(nullptr, nullptr);
2773 // Clear the original URI so SetDocumentURI sets it.
2774 mOriginalURI = nullptr;
2776 SetDocumentURI(aURI);
2777 mChromeXHRDocURI = nullptr;
2778 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2779 // mDocumentURI.
2780 mDocumentBaseURI = nullptr;
2781 mChromeXHRDocBaseURI = nullptr;
2783 // Check if the current document is the top-level DevTools document.
2784 // For inner DevTools frames, mIsDevToolsDocument will be set when
2785 // calling SetDocumentParent.
2786 if (aURI && aURI->SchemeIs("about") &&
2787 aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) {
2788 mIsDevToolsDocument = true;
2791 if (aLoadGroup) {
2792 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2793 // there was an assertion here that aLoadGroup was not null. This
2794 // is no longer valid: nsDocShell::SetDocument does not create a
2795 // load group, and it works just fine
2797 // XXXbz what does "just fine" mean exactly? And given that there
2798 // is no nsDocShell::SetDocument, what is this talking about?
2800 if (IsContentDocument()) {
2801 // Inform the associated request context about this load start so
2802 // any of its internal load progress flags gets reset.
2803 nsCOMPtr<nsIRequestContextService> rcsvc =
2804 net::RequestContextService::GetOrCreate();
2805 if (rcsvc) {
2806 nsCOMPtr<nsIRequestContext> rc;
2807 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2808 if (rc) {
2809 rc->BeginLoad();
2815 mLastModified.Truncate();
2816 // XXXbz I guess we're assuming that the caller will either pass in
2817 // a channel with a useful type or call SetContentType?
2818 SetContentTypeInternal(""_ns);
2819 mContentLanguage.Truncate();
2820 mBaseTarget.Truncate();
2822 mXMLDeclarationBits = 0;
2824 // Now get our new principal
2825 if (aPrincipal) {
2826 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2827 } else {
2828 nsIScriptSecurityManager* securityManager =
2829 nsContentUtils::GetSecurityManager();
2830 if (securityManager) {
2831 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
2833 if (!loadContext && aLoadGroup) {
2834 nsCOMPtr<nsIInterfaceRequestor> cbs;
2835 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
2836 loadContext = do_GetInterface(cbs);
2839 MOZ_ASSERT(loadContext,
2840 "must have a load context or pass in an explicit principal");
2842 nsCOMPtr<nsIPrincipal> principal;
2843 nsresult rv = securityManager->GetLoadContextContentPrincipal(
2844 mDocumentURI, loadContext, getter_AddRefs(principal));
2845 if (NS_SUCCEEDED(rv)) {
2846 SetPrincipals(principal, principal);
2851 if (mFontFaceSet) {
2852 mFontFaceSet->RefreshStandardFontLoadPrincipal();
2855 // Refresh the principal on the realm.
2856 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
2857 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
2861 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
2862 nsIPrincipal* aPrincipal) {
2863 if (!aPrincipal) {
2864 return nullptr;
2867 // We can't load a document with an expanded principal. If we're given one,
2868 // automatically downgrade it to the last principal it subsumes (which is the
2869 // extension principal, in the case of extension content scripts).
2870 auto* basePrin = BasePrincipal::Cast(aPrincipal);
2871 if (basePrin->Is<ExpandedPrincipal>()) {
2872 MOZ_DIAGNOSTIC_ASSERT(false,
2873 "Should never try to create a document with "
2874 "an expanded principal");
2876 auto* expanded = basePrin->As<ExpandedPrincipal>();
2877 return do_AddRef(expanded->AllowList().LastElement());
2880 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
2881 // We basically want the parent document here, but because this is very
2882 // early in the load, GetInProcessParentDocument() returns null, so we use
2883 // the docshell hierarchy to get this information instead.
2884 if (RefPtr<BrowsingContext> parent =
2885 mDocumentContainer->GetBrowsingContext()->GetParent()) {
2886 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
2887 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
2888 nsCOMPtr<nsIPrincipal> nullPrincipal =
2889 NullPrincipal::CreateWithoutOriginAttributes();
2890 return nullPrincipal.forget();
2894 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
2895 return principal.forget();
2898 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
2899 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
2901 // lowest index first
2902 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
2904 size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
2905 size_t index = 0;
2906 for (; index < count; index++) {
2907 auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
2908 MOZ_ASSERT(sheet);
2909 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
2910 if (sheetDocIndex > newDocIndex) {
2911 break;
2914 // If the sheet is not owned by the document it can be an author
2915 // sheet registered at nsStyleSheetService or an additional author
2916 // sheet on the document, which means the new
2917 // doc sheet should end up before it.
2918 if (sheetDocIndex < 0) {
2919 if (sheetService) {
2920 auto& authorSheets = *sheetService->AuthorStyleSheets();
2921 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
2922 break;
2925 if (sheet == GetFirstAdditionalAuthorSheet()) {
2926 break;
2931 return index;
2934 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
2935 MOZ_ASSERT(aURI);
2937 ClearAdoptedStyleSheets();
2939 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
2940 for (auto& sheet : Reversed(aSheetList)) {
2941 sheet->ClearAssociatedDocumentOrShadowRoot();
2942 if (mStyleSetFilled) {
2943 mStyleSet->RemoveStyleSheet(*sheet);
2946 aSheetList.Clear();
2948 ClearSheetList(mStyleSheets);
2949 for (auto& sheets : mAdditionalSheets) {
2950 ClearSheetList(sheets);
2952 if (mStyleSetFilled) {
2953 if (auto* ss = nsStyleSheetService::GetInstance()) {
2954 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
2955 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
2956 if (sheet->IsApplicable()) {
2957 mStyleSet->RemoveStyleSheet(*sheet);
2963 // Now reset our inline style and attribute sheets.
2964 if (mAttrStyleSheet) {
2965 mAttrStyleSheet->Reset();
2966 mAttrStyleSheet->SetOwningDocument(this);
2967 } else {
2968 mAttrStyleSheet = new nsHTMLStyleSheet(this);
2971 if (!mStyleAttrStyleSheet) {
2972 mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet();
2975 if (mStyleSetFilled) {
2976 FillStyleSetDocumentSheets();
2978 if (mStyleSet->StyleSheetsHaveChanged()) {
2979 ApplicableStylesChanged();
2984 static void AppendSheetsToStyleSet(
2985 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
2986 for (StyleSheet* sheet : Reversed(aSheets)) {
2987 aStyleSet->AppendStyleSheet(*sheet);
2991 void Document::FillStyleSetUserAndUASheets() {
2992 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
2993 // ordering.
2995 // The document will fill in the document sheets when we create the presshell
2996 auto* cache = GlobalStyleSheetCache::Singleton();
2998 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
2999 MOZ_ASSERT(sheetService,
3000 "should never be creating a StyleSet after the style sheet "
3001 "service has gone");
3003 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3004 mStyleSet->AppendStyleSheet(*sheet);
3007 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3008 : cache->GetUserContentSheet();
3009 if (sheet) {
3010 mStyleSet->AppendStyleSheet(*sheet);
3013 mStyleSet->AppendStyleSheet(*cache->UASheet());
3015 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3016 mStyleSet->AppendStyleSheet(*cache->MathMLSheet());
3019 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3020 mStyleSet->AppendStyleSheet(*cache->SVGSheet());
3023 mStyleSet->AppendStyleSheet(*cache->HTMLSheet());
3025 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3026 mStyleSet->AppendStyleSheet(*cache->NoFramesSheet());
3029 if (nsLayoutUtils::ShouldUseNoScriptSheet(this)) {
3030 mStyleSet->AppendStyleSheet(*cache->NoScriptSheet());
3033 mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet());
3035 // Load the minimal XUL rules for scrollbars and a few other XUL things
3036 // that non-XUL (typically HTML) documents commonly use.
3037 mStyleSet->AppendStyleSheet(*cache->MinimalXULSheet());
3039 // Only load the full XUL sheet if we'll need it.
3040 if (LoadsFullXULStyleSheetUpFront()) {
3041 mStyleSet->AppendStyleSheet(*cache->XULSheet());
3044 mStyleSet->AppendStyleSheet(*cache->FormsSheet());
3045 mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet());
3047 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3048 mStyleSet->AppendStyleSheet(*sheet);
3051 MOZ_ASSERT(!mQuirkSheetAdded);
3052 if (NeedsQuirksSheet()) {
3053 mStyleSet->AppendStyleSheet(*cache->QuirkSheet());
3054 mQuirkSheetAdded = true;
3058 void Document::FillStyleSet() {
3059 MOZ_ASSERT(!mStyleSetFilled);
3060 FillStyleSetUserAndUASheets();
3061 FillStyleSetDocumentSheets();
3062 mStyleSetFilled = true;
3065 void Document::RemoveContentEditableStyleSheets() {
3066 MOZ_ASSERT(IsHTMLOrXHTML());
3068 auto* cache = GlobalStyleSheetCache::Singleton();
3069 bool changed = false;
3070 if (mDesignModeSheetAdded) {
3071 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3072 mDesignModeSheetAdded = false;
3073 changed = true;
3075 if (mContentEditableSheetAdded) {
3076 mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet());
3077 mContentEditableSheetAdded = false;
3078 changed = true;
3080 if (changed) {
3081 MOZ_ASSERT(mStyleSetFilled);
3082 ApplicableStylesChanged();
3086 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
3087 MOZ_ASSERT(IsHTMLOrXHTML());
3088 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3089 "Caller should ensure we're being rendered");
3091 auto* cache = GlobalStyleSheetCache::Singleton();
3092 bool changed = false;
3093 if (!mContentEditableSheetAdded) {
3094 mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet());
3095 mContentEditableSheetAdded = true;
3096 changed = true;
3098 if (mDesignModeSheetAdded != aDesignMode) {
3099 if (mDesignModeSheetAdded) {
3100 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3101 } else {
3102 mStyleSet->AppendStyleSheet(*cache->DesignModeSheet());
3104 mDesignModeSheetAdded = !mDesignModeSheetAdded;
3105 changed = true;
3107 if (changed) {
3108 ApplicableStylesChanged();
3112 void Document::FillStyleSetDocumentSheets() {
3113 MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
3114 "Style set already has document sheets?");
3116 // Sheets are added in reverse order to avoid worst-case time complexity when
3117 // looking up the index of a sheet.
3119 // Note that usually appending is faster (rebuilds less stuff in the
3120 // styleset), but in this case it doesn't matter since we're filling the
3121 // styleset from scratch anyway.
3122 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3123 if (sheet->IsApplicable()) {
3124 mStyleSet->AddDocStyleSheet(*sheet);
3128 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3129 if (aSheet.IsApplicable()) {
3130 mStyleSet->AddDocStyleSheet(aSheet);
3134 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3135 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3136 mStyleSet->AppendStyleSheet(*sheet);
3139 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]);
3140 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]);
3141 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]);
3144 void Document::CompatibilityModeChanged() {
3145 MOZ_ASSERT(IsHTMLOrXHTML());
3146 CSSLoader()->SetCompatibilityMode(mCompatMode);
3147 mStyleSet->CompatibilityModeChanged();
3148 if (PresShell* presShell = GetPresShell()) {
3149 // Selectors may have become case-sensitive / case-insensitive, the stylist
3150 // has already performed the relevant invalidation.
3151 presShell->EnsureStyleFlush();
3153 if (!mStyleSetFilled) {
3154 MOZ_ASSERT(!mQuirkSheetAdded);
3155 return;
3157 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3158 return;
3160 auto* cache = GlobalStyleSheetCache::Singleton();
3161 StyleSheet* sheet = cache->QuirkSheet();
3162 if (mQuirkSheetAdded) {
3163 mStyleSet->RemoveStyleSheet(*sheet);
3164 } else {
3165 mStyleSet->AppendStyleSheet(*sheet);
3167 mQuirkSheetAdded = !mQuirkSheetAdded;
3168 ApplicableStylesChanged();
3171 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3172 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3173 "Bad compat mode for XHTML document!");
3175 if (mCompatMode == aMode) {
3176 return;
3178 mCompatMode = aMode;
3179 CompatibilityModeChanged();
3180 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3181 mViewportType = Unknown;
3184 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3185 uint32_t aSandboxFlags,
3186 nsIChannel* aChannel) {
3187 // If the document permits allow-top-navigation and
3188 // allow-top-navigation-by-user-activation this will permit all top
3189 // navigation.
3190 if (aSandboxFlags != SANDBOXED_NONE &&
3191 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3192 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3193 nsContentUtils::ReportToConsole(
3194 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3195 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3196 "BothAllowTopNavigationAndUserActivationPresent");
3198 // If the document is sandboxed (via the HTML5 iframe sandbox
3199 // attribute) and both the allow-scripts and allow-same-origin
3200 // keywords are supplied, the sandboxed document can call into its
3201 // parent document and remove its sandboxing entirely - we print a
3202 // warning to the web console in this case.
3203 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3204 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3205 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3206 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3207 MOZ_ASSERT(bc->IsInProcess());
3209 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3210 if (!parentBC || !parentBC->IsInProcess()) {
3211 // If parent document is not in process, then by construction it
3212 // cannot be same origin.
3213 return;
3216 // Don't warn if our parent is not the top-level document.
3217 if (!parentBC->IsTopContent()) {
3218 return;
3221 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3222 MOZ_ASSERT(parentDocShell);
3224 nsCOMPtr<nsIChannel> parentChannel;
3225 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3226 if (!parentChannel) {
3227 return;
3229 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3230 if (NS_FAILED(rv)) {
3231 return;
3234 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3235 nsCOMPtr<nsIURI> iframeUri;
3236 parentChannel->GetURI(getter_AddRefs(iframeUri));
3237 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3238 "Iframe Sandbox"_ns, parentDocument,
3239 nsContentUtils::eSECURITY_PROPERTIES,
3240 "BothAllowScriptsAndSameOriginPresent",
3241 nsTArray<nsString>(), iframeUri);
3245 bool Document::IsSynthesized() {
3246 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3247 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3250 // static
3251 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3252 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3253 return principal && (principal->IsSystemPrincipal() ||
3254 principal->GetIsAddonOrExpandedAddonPrincipal());
3257 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3258 nsILoadGroup* aLoadGroup,
3259 nsISupports* aContainer,
3260 nsIStreamListener** aDocListener,
3261 bool aReset, nsIContentSink* aSink) {
3262 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3263 nsCOMPtr<nsIURI> uri;
3264 aChannel->GetURI(getter_AddRefs(uri));
3265 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3266 ("DOCUMENT %p StartDocumentLoad %s", this,
3267 uri ? uri->GetSpecOrDefault().get() : ""));
3270 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3271 "Bad readyState");
3272 SetReadyStateInternal(READYSTATE_LOADING);
3274 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3275 mLoadedAsData = true;
3276 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3277 // We need to disable script & style loading in this case.
3278 // We leave them disabled even in EndLoad(), and let anyone
3279 // who puts the document on display to worry about enabling.
3281 // Do not load/process scripts when loading as data
3282 ScriptLoader()->SetEnabled(false);
3284 // styles
3285 CSSLoader()->SetEnabled(
3286 false); // Do not load/process styles when loading as data
3287 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3288 // Allow CSS, but not scripts
3289 ScriptLoader()->SetEnabled(false);
3292 mMayStartLayout = false;
3293 MOZ_ASSERT(!mReadyForIdle,
3294 "We should never hit DOMContentLoaded before this point");
3296 if (aReset) {
3297 Reset(aChannel, aLoadGroup);
3300 nsAutoCString contentType;
3301 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3302 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3303 contentType))) ||
3304 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3305 // XXX this is only necessary for viewsource:
3306 nsACString::const_iterator start, end, semicolon;
3307 contentType.BeginReading(start);
3308 contentType.EndReading(end);
3309 semicolon = start;
3310 FindCharInReadable(';', semicolon, end);
3311 SetContentTypeInternal(Substring(start, semicolon));
3314 RetrieveRelevantHeaders(aChannel);
3316 mChannel = aChannel;
3317 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3318 if (inStrmChan) {
3319 bool isSrcdocChannel;
3320 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3321 if (isSrcdocChannel) {
3322 mIsSrcdocDocument = true;
3326 if (mChannel) {
3327 nsLoadFlags loadFlags;
3328 mChannel->GetLoadFlags(&loadFlags);
3329 bool isDocument = false;
3330 mChannel->GetIsDocument(&isDocument);
3331 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3332 IsSynthesized() && XRE_IsContentProcess()) {
3333 ContentChild::UpdateCookieStatus(mChannel);
3336 // Store the security info for future use.
3337 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3340 // If this document is being loaded by a docshell, copy its sandbox flags
3341 // to the document, and store the fullscreen enabled flag. These are
3342 // immutable after being set here.
3343 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3345 // If this is an error page, don't inherit sandbox flags
3346 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3347 if (docShell && !loadInfo->GetLoadErrorPage()) {
3348 mSandboxFlags = loadInfo->GetSandboxFlags();
3349 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3352 // Set the opener policy for the top level content document.
3353 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3354 nsILoadInfo::CrossOriginOpenerPolicy policy =
3355 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3356 if (IsTopLevelContentDocument() && httpChan &&
3357 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3358 docShell->GetBrowsingContext()) {
3359 // Setting the opener policy on a discarded context has no effect.
3360 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3363 // The CSP directives upgrade-insecure-requests as well as
3364 // block-all-mixed-content not only apply to the toplevel document,
3365 // but also to nested documents. The loadInfo of a subdocument
3366 // load already holds the correct flag, so let's just set it here
3367 // on the document. Please note that we set the appropriate preload
3368 // bits just for the sake of completeness here, because the preloader
3369 // does not reach into subdocuments.
3370 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3371 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3372 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3373 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3375 // HTTPS-Only Mode flags
3376 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3377 // sub-resources and sub-documents.
3378 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3380 nsresult rv = InitReferrerInfo(aChannel);
3381 NS_ENSURE_SUCCESS(rv, rv);
3383 rv = InitCOEP(aChannel);
3384 NS_ENSURE_SUCCESS(rv, rv);
3386 // Check CSP navigate-to
3387 // We need to enforce the CSP of the document that initiated the load,
3388 // which is the CSP to inherit.
3389 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3390 if (cspToInherit) {
3391 bool allowsNavigateTo = false;
3392 rv = cspToInherit->GetAllowsNavigateTo(
3393 mDocumentURI, loadInfo->GetIsFormSubmission(),
3394 !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
3395 true, /* aEnforceWhitelist */
3396 &allowsNavigateTo);
3397 NS_ENSURE_SUCCESS(rv, rv);
3399 if (!allowsNavigateTo) {
3400 aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
3401 return NS_OK;
3405 rv = InitCSP(aChannel);
3406 NS_ENSURE_SUCCESS(rv, rv);
3408 // Initialize FeaturePolicy
3409 rv = InitFeaturePolicy(aChannel);
3410 NS_ENSURE_SUCCESS(rv, rv);
3412 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3413 NS_ENSURE_SUCCESS(rv, rv);
3415 // Generally XFO and CSP frame-ancestors is handled within
3416 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3417 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3418 nsContentPolicyType internalContentType =
3419 loadInfo->InternalContentPolicyType();
3420 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3421 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3422 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3424 nsresult status;
3425 aChannel->GetStatus(&status);
3426 if (status == NS_ERROR_XFO_VIOLATION) {
3427 // stop! ERROR page!
3428 // But before we have to reset the principal of the document
3429 // because the onload() event fires before the error page
3430 // is displayed and we do not want the enclosing document
3431 // to access the contentDocument.
3432 RefPtr<NullPrincipal> nullPrincipal =
3433 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3434 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3435 // and also GetInnerWindow() is still null at this point, before
3436 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3437 // within Document.cpp
3438 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3439 SetPrincipals(nullPrincipal, nullPrincipal);
3443 return NS_OK;
3446 void Document::SetLoadedAsData(bool aLoadedAsData,
3447 bool aConsiderForMemoryReporting) {
3448 mLoadedAsData = aLoadedAsData;
3449 if (aConsiderForMemoryReporting) {
3450 nsIGlobalObject* global = GetScopeObject();
3451 if (global) {
3452 if (nsPIDOMWindowInner* window = global->AsInnerWindow()) {
3453 nsGlobalWindowInner::Cast(window)
3454 ->RegisterDataDocumentForMemoryReporting(this);
3460 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3462 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3464 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3465 return mPreloadCSP;
3468 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3469 mPreloadCSP = aPreloadCSP;
3472 void Document::GetCspJSON(nsString& aJSON) {
3473 aJSON.Truncate();
3475 if (!mCSP) {
3476 dom::CSPPolicies jsonPolicies;
3477 jsonPolicies.ToJSON(aJSON);
3478 return;
3480 mCSP->ToJSON(aJSON);
3483 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3484 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3485 nsAutoString messageTag;
3486 aMessages[i]->GetTag(messageTag);
3488 nsAutoString category;
3489 aMessages[i]->GetCategory(category);
3491 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3492 NS_ConvertUTF16toUTF8(category), this,
3493 nsContentUtils::eSECURITY_PROPERTIES,
3494 NS_ConvertUTF16toUTF8(messageTag).get());
3498 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3499 nsresult rv = NS_OK;
3500 if (!aSpeculative) {
3501 // 1) apply settings from regular CSP
3502 if (mCSP) {
3503 // Set up 'block-all-mixed-content' if not already inherited
3504 // from the parent context or set by any other CSP.
3505 if (!mBlockAllMixedContent) {
3506 bool block = false;
3507 rv = mCSP->GetBlockAllMixedContent(&block);
3508 NS_ENSURE_SUCCESS_VOID(rv);
3509 mBlockAllMixedContent = block;
3511 if (!mBlockAllMixedContentPreloads) {
3512 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3515 // Set up 'upgrade-insecure-requests' if not already inherited
3516 // from the parent context or set by any other CSP.
3517 if (!mUpgradeInsecureRequests) {
3518 bool upgrade = false;
3519 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3520 NS_ENSURE_SUCCESS_VOID(rv);
3521 mUpgradeInsecureRequests = upgrade;
3523 if (!mUpgradeInsecurePreloads) {
3524 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3526 // Update csp settings in the parent process
3527 if (auto* wgc = GetWindowGlobalChild()) {
3528 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3529 mUpgradeInsecureRequests);
3532 return;
3535 // 2) apply settings from speculative csp
3536 if (mPreloadCSP) {
3537 if (!mBlockAllMixedContentPreloads) {
3538 bool block = false;
3539 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3540 NS_ENSURE_SUCCESS_VOID(rv);
3541 mBlockAllMixedContent = block;
3543 if (!mUpgradeInsecurePreloads) {
3544 bool upgrade = false;
3545 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3546 NS_ENSURE_SUCCESS_VOID(rv);
3547 mUpgradeInsecurePreloads = upgrade;
3552 nsresult Document::InitCSP(nsIChannel* aChannel) {
3553 MOZ_ASSERT(!mScriptGlobalObject,
3554 "CSP must be initialized before mScriptGlobalObject is set!");
3555 if (!StaticPrefs::security_csp_enable()) {
3556 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3557 ("CSP is disabled, skipping CSP init for document %p", this));
3558 return NS_OK;
3561 // If this is a data document - no need to set CSP.
3562 if (mLoadedAsData) {
3563 return NS_OK;
3566 // If this is an image, no need to set a CSP. Otherwise SVG images
3567 // served with a CSP might block internally applied inline styles.
3568 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3569 if (loadInfo->GetExternalContentPolicyType() ==
3570 ExtContentPolicy::TYPE_IMAGE) {
3571 return NS_OK;
3574 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3576 // If there is a CSP that needs to be inherited from whatever
3577 // global is considered the client of the document fetch then
3578 // we query it here from the loadinfo in case the newly created
3579 // document needs to inherit the CSP. See:
3580 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3581 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3582 if (inheritedCSP) {
3583 mCSP = loadInfo->GetCspToInherit();
3586 // If there is no CSP to inherit, then we create a new CSP here so
3587 // that history entries always have the right reference in case a
3588 // Meta CSP gets dynamically added after the history entry has
3589 // already been created.
3590 if (!mCSP) {
3591 mCSP = new nsCSPContext();
3594 // Always overwrite the requesting context of the CSP so that any new
3595 // 'self' keyword added to an inherited CSP translates correctly.
3596 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3597 if (NS_WARN_IF(NS_FAILED(rv))) {
3598 return rv;
3601 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3603 nsCOMPtr<nsIHttpChannel> httpChannel;
3604 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3605 if (NS_WARN_IF(NS_FAILED(rv))) {
3606 return rv;
3609 if (httpChannel) {
3610 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3611 tCspHeaderValue);
3613 Unused << httpChannel->GetResponseHeader(
3614 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3616 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3617 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3619 // Check if this is a document from a WebExtension.
3620 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3621 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3623 // If there's no CSP to apply, go ahead and return early
3624 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3625 cspROHeaderValue.IsEmpty()) {
3626 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3627 nsCOMPtr<nsIURI> chanURI;
3628 aChannel->GetURI(getter_AddRefs(chanURI));
3629 nsAutoCString aspec;
3630 chanURI->GetAsciiSpec(aspec);
3631 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3632 ("no CSP for document, %s", aspec.get()));
3635 return NS_OK;
3638 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3639 ("Document is an add-on or CSP header specified %p", this));
3641 // ----- if the doc is an addon, apply its CSP.
3642 if (addonPolicy) {
3643 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3645 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3646 // Bug 1548468: Move CSP off ExpandedPrincipal
3647 // Currently the LoadInfo holds the source of truth for every resource load
3648 // because LoadInfo::GetCSP() queries the CSP from an ExpandedPrincipal
3649 // (and not from the Client) if the load was triggered by an extension.
3650 auto* basePrin = BasePrincipal::Cast(principal);
3651 if (basePrin->Is<ExpandedPrincipal>()) {
3652 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3656 // ----- if there's a full-strength CSP header, apply it.
3657 if (!cspHeaderValue.IsEmpty()) {
3658 mHasCSPDeliveredThroughHeader = true;
3659 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3660 NS_ENSURE_SUCCESS(rv, rv);
3663 // ----- if there's a report-only CSP header, apply it.
3664 if (!cspROHeaderValue.IsEmpty()) {
3665 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3666 NS_ENSURE_SUCCESS(rv, rv);
3669 // ----- Enforce sandbox policy if supplied in CSP header
3670 // The document may already have some sandbox flags set (e.g. if the document
3671 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3672 // directive, intersect the CSP sandbox flags with the existing flags. This
3673 // corresponds to the _least_ permissive policy.
3674 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3675 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3676 NS_ENSURE_SUCCESS(rv, rv);
3678 // Probably the iframe sandbox attribute already caused the creation of a
3679 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3680 // and no one has been created yet.
3681 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3682 !(mSandboxFlags & SANDBOXED_ORIGIN);
3684 mSandboxFlags |= cspSandboxFlags;
3686 if (needNewNullPrincipal) {
3687 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3688 // Skip setting the content blocking allowlist principal to NullPrincipal.
3689 // The principal is only used to enable/disable trackingprotection via
3690 // permission and can be shared with the top level sandboxed site.
3691 // See Bug 1654546.
3692 SetPrincipals(principal, principal);
3695 ApplySettingsFromCSP(false);
3696 return NS_OK;
3699 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3700 BrowsingContext* browsingContext = GetBrowsingContext();
3701 if (!browsingContext) {
3702 return nullptr;
3704 if (!browsingContext->IsContentSubframe()) {
3705 return nullptr;
3708 HTMLIFrameElement* iframe =
3709 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3710 if (iframe) {
3711 return do_AddRef(iframe->FeaturePolicy());
3714 if (XRE_IsParentProcess()) {
3715 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3718 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3719 if (!windowContext) {
3720 return nullptr;
3723 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3724 if (!child) {
3725 return nullptr;
3728 return do_AddRef(child->GetContainerFeaturePolicy());
3731 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3732 MOZ_ASSERT(mFeaturePolicy, "we should only call init once");
3734 mFeaturePolicy->ResetDeclaredPolicy();
3736 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3738 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3739 if (parentPolicy) {
3740 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3741 mFeaturePolicy->InheritPolicy(parentPolicy);
3742 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3745 // We don't want to parse the http Feature-Policy header if this pref is off.
3746 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3747 return NS_OK;
3750 nsCOMPtr<nsIHttpChannel> httpChannel;
3751 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3752 if (NS_WARN_IF(NS_FAILED(rv))) {
3753 return rv;
3756 if (!httpChannel) {
3757 return NS_OK;
3760 // query the policy from the header
3761 nsAutoCString value;
3762 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3763 if (NS_SUCCEEDED(rv)) {
3764 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3765 NodePrincipal(), nullptr);
3768 return NS_OK;
3771 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3772 MOZ_ASSERT(mReferrerInfo);
3773 MOZ_ASSERT(mPreloadReferrerInfo);
3775 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3776 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
3777 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
3778 // they have an opaque origin.
3779 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
3780 if (BrowsingContext* bc = GetBrowsingContext()) {
3781 // At this point the document is not fully created and mParentDocument has
3782 // not been set yet,
3783 Document* parentDoc = bc->GetEmbedderElement()
3784 ? bc->GetEmbedderElement()->OwnerDoc()
3785 : nullptr;
3786 if (parentDoc) {
3787 mReferrerInfo = parentDoc->GetReferrerInfo();
3788 mPreloadReferrerInfo = mReferrerInfo;
3789 return NS_OK;
3792 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
3793 "srcdoc without null principal as toplevel!");
3797 nsCOMPtr<nsIHttpChannel> httpChannel;
3798 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3799 if (NS_WARN_IF(NS_FAILED(rv))) {
3800 return rv;
3803 if (!httpChannel) {
3804 return NS_OK;
3807 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
3808 if (referrerInfo) {
3809 mReferrerInfo = referrerInfo;
3812 // Override policy if we get one from Referrerr-Policy header
3813 mozilla::dom::ReferrerPolicy policy =
3814 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
3815 mReferrerInfo = static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
3816 ->CloneWithNewPolicy(policy);
3818 mPreloadReferrerInfo = mReferrerInfo;
3819 return NS_OK;
3822 nsresult Document::InitCOEP(nsIChannel* aChannel) {
3823 nsCOMPtr<nsIHttpChannel> httpChannel;
3824 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3825 if (NS_FAILED(rv)) {
3826 return NS_OK;
3829 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
3831 if (!intChannel) {
3832 return NS_OK;
3835 nsILoadInfo::CrossOriginEmbedderPolicy policy =
3836 nsILoadInfo::EMBEDDER_POLICY_NULL;
3837 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(&policy))) {
3838 mEmbedderPolicy = Some(policy);
3841 return NS_OK;
3844 void Document::StopDocumentLoad() {
3845 if (mParser) {
3846 mParserAborted = true;
3847 mParser->Terminate();
3851 void Document::SetDocumentURI(nsIURI* aURI) {
3852 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
3853 mDocumentURI = aURI;
3854 nsIURI* newBase = GetDocBaseURI();
3856 mDocURISchemeIsChrome = aURI && IsChromeURI(aURI);
3858 bool equalBases = false;
3859 // Changing just the ref of a URI does not change how relative URIs would
3860 // resolve wrt to it, so we can treat the bases as equal as long as they're
3861 // equal ignoring the ref.
3862 if (oldBase && newBase) {
3863 oldBase->EqualsExceptRef(newBase, &equalBases);
3864 } else {
3865 equalBases = !oldBase && !newBase;
3868 // If this is the first time we're setting the document's URI, set the
3869 // document's original URI.
3870 if (!mOriginalURI) mOriginalURI = mDocumentURI;
3872 // If changing the document's URI changed the base URI of the document, we
3873 // need to refresh the hrefs of all the links on the page.
3874 if (!equalBases) {
3875 RefreshLinkHrefs();
3878 // Recalculate our base domain
3879 mBaseDomain.Truncate();
3880 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
3881 if (thirdPartyUtil) {
3882 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
3885 // Tell our WindowGlobalParent that the document's URI has been changed.
3886 nsPIDOMWindowInner* inner = GetInnerWindow();
3887 if (inner && inner->GetWindowGlobalChild()) {
3888 inner->GetWindowGlobalChild()->SetDocumentURI(mDocumentURI);
3892 static void GetFormattedTimeString(PRTime aTime,
3893 nsAString& aFormattedTimeString) {
3894 PRExplodedTime prtime;
3895 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
3896 // "MM/DD/YYYY hh:mm:ss"
3897 char formatedTime[24];
3898 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
3899 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
3900 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
3901 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
3902 } else {
3903 // If we for whatever reason failed to find the last modified time
3904 // (or even the current time), fall back to what NS4.x returned.
3905 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
3909 void Document::GetLastModified(nsAString& aLastModified) const {
3910 if (!mLastModified.IsEmpty()) {
3911 aLastModified.Assign(mLastModified);
3912 } else {
3913 GetFormattedTimeString(PR_Now(), aLastModified);
3917 static void IncrementExpandoGeneration(Document& aDoc) {
3918 ++aDoc.mExpandoAndGeneration.generation;
3921 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
3922 MOZ_ASSERT(
3923 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
3924 "Only put elements that need to be exposed as document['name'] in "
3925 "the named table.");
3927 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
3929 // Null for out-of-memory
3930 if (entry) {
3931 if (!entry->HasNameElement() &&
3932 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
3933 IncrementExpandoGeneration(*this);
3935 entry->AddNameElement(this, aElement);
3939 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
3940 // Speed up document teardown
3941 if (mIdentifierMap.Count() == 0) return;
3943 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
3944 if (!entry) // Could be false if the element was anonymous, hence never added
3945 return;
3947 entry->RemoveNameElement(aElement);
3948 if (!entry->HasNameElement() &&
3949 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
3950 IncrementExpandoGeneration(*this);
3954 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
3955 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
3957 if (entry) { /* True except on OOM */
3958 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
3959 !entry->HasNameElement() &&
3960 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
3961 IncrementExpandoGeneration(*this);
3963 entry->AddIdElement(aElement);
3967 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
3968 NS_ASSERTION(aId, "huhwhatnow?");
3970 // Speed up document teardown
3971 if (mIdentifierMap.Count() == 0) {
3972 return;
3975 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
3976 if (!entry) // Can be null for XML elements with changing ids.
3977 return;
3979 entry->RemoveIdElement(aElement);
3980 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
3981 !entry->HasNameElement() &&
3982 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
3983 IncrementExpandoGeneration(*this);
3985 if (entry->IsEmpty()) {
3986 mIdentifierMap.RemoveEntry(entry);
3990 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
3991 bool aPreload) {
3992 ReferrerPolicyEnum policy =
3993 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
3994 // The empty string "" corresponds to no referrer policy, causing a fallback
3995 // to a referrer policy defined elsewhere.
3996 if (policy == ReferrerPolicy::_empty) {
3997 return;
4000 MOZ_ASSERT(mReferrerInfo);
4001 MOZ_ASSERT(mPreloadReferrerInfo);
4003 if (aPreload) {
4004 mPreloadReferrerInfo =
4005 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4006 ->CloneWithNewPolicy(policy);
4007 } else {
4008 mReferrerInfo =
4009 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4010 ->CloneWithNewPolicy(policy);
4014 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4015 nsIPrincipal* aNewPartitionedPrincipal) {
4016 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4017 if (aNewPrincipal && mAllowDNSPrefetch &&
4018 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4019 if (aNewPrincipal->SchemeIs("https")) {
4020 mAllowDNSPrefetch = false;
4024 mCSSLoader->DeregisterFromSheetCache();
4026 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4027 mPartitionedPrincipal = aNewPartitionedPrincipal;
4029 mCSSLoader->RegisterInSheetCache();
4031 #ifdef DEBUG
4032 // Validate that the docgroup is set correctly by calling its getter and
4033 // triggering its sanity check.
4035 // If we're setting the principal to null, we don't want to perform the check,
4036 // as the document is entering an intermediate state where it does not have a
4037 // principal. It will be given another real principal shortly which we will
4038 // check. It's not unsafe to have a document which has a null principal in the
4039 // same docgroup as another document, so this should not be a problem.
4040 if (aNewPrincipal) {
4041 GetDocGroup();
4043 #endif
4046 #ifdef DEBUG
4047 void Document::AssertDocGroupMatchesKey() const {
4048 // Sanity check that we have an up-to-date and accurate docgroup
4049 // We only check if the principal when we can get the browsing context.
4050 if (!GetBrowsingContext()) {
4051 return;
4054 if (mDocGroup) {
4055 nsAutoCString docGroupKey;
4057 // GetKey() can fail, e.g. after the TLD service has shut down.
4058 nsresult rv = mozilla::dom::DocGroup::GetKey(
4059 NodePrincipal(), CrossOriginIsolated(), docGroupKey);
4060 if (NS_SUCCEEDED(rv)) {
4061 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4065 #endif
4067 nsresult Document::Dispatch(TaskCategory aCategory,
4068 already_AddRefed<nsIRunnable>&& aRunnable) {
4069 // Note that this method may be called off the main thread.
4070 if (mDocGroup) {
4071 return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
4073 return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
4076 nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
4077 if (mDocGroup) {
4078 return mDocGroup->EventTargetFor(aCategory);
4080 return DispatcherTrait::EventTargetFor(aCategory);
4083 AbstractThread* Document::AbstractMainThreadFor(
4084 mozilla::TaskCategory aCategory) {
4085 MOZ_ASSERT(NS_IsMainThread());
4086 if (mDocGroup) {
4087 return mDocGroup->AbstractMainThreadFor(aCategory);
4089 return DispatcherTrait::AbstractMainThreadFor(aCategory);
4092 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4093 bool aIsTracking) {
4094 if (aIsTracking) {
4095 mTrackingScripts.Insert(aURL);
4096 } else {
4097 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4101 bool Document::IsScriptTracking(JSContext* aCx) const {
4102 JS::AutoFilename filename;
4103 uint32_t line = 0;
4104 uint32_t column = 0;
4105 if (!JS::DescribeScriptedCaller(aCx, &filename, &line, &column)) {
4106 return false;
4108 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4111 void Document::GetContentType(nsAString& aContentType) {
4112 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4115 void Document::SetContentType(const nsAString& aContentType) {
4116 SetContentTypeInternal(NS_ConvertUTF16toUTF8(aContentType));
4119 bool Document::GetAllowPlugins() {
4120 // First, we ask our docshell if it allows plugins.
4121 auto* browsingContext = GetBrowsingContext();
4123 if (browsingContext) {
4124 if (!browsingContext->GetAllowPlugins()) {
4125 return false;
4128 // If the docshell allows plugins, we check whether
4129 // we are sandboxed and plugins should not be allowed.
4130 if (mSandboxFlags & SANDBOXED_PLUGINS) {
4131 return false;
4135 FlashClassification classification = DocumentFlashClassification();
4136 if (classification == FlashClassification::Denied) {
4137 return false;
4140 return true;
4143 bool Document::HasPendingInitialTranslation() {
4144 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4147 DocumentL10n* Document::GetL10n() { return mDocumentL10n; }
4149 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4150 JS::Rooted<JSObject*> object(aCx, aObject);
4151 nsCOMPtr<nsIPrincipal> callerPrincipal =
4152 nsContentUtils::SubjectPrincipal(aCx);
4153 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4154 bool allowed = false;
4155 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4156 &allowed);
4157 return allowed;
4160 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4161 if (!AllowsL10n()) {
4162 return;
4165 nsAutoString href;
4166 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
4168 if (!mDocumentL10n) {
4169 Element* elem = GetDocumentElement();
4170 MOZ_DIAGNOSTIC_ASSERT(elem);
4172 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4173 mDocumentL10n = DocumentL10n::Create(this, isSync);
4174 MOZ_ASSERT(mDocumentL10n);
4176 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4178 if (mReadyState >= READYSTATE_INTERACTIVE) {
4179 mDocumentL10n->TriggerInitialTranslation();
4180 } else {
4181 if (!mDocumentL10n->mBlockingLayout) {
4182 // Our initial translation is going to block layout start. Make sure
4183 // we don't fire the load event until after that stops happening and
4184 // layout has a chance to start.
4185 BlockOnload();
4186 mDocumentL10n->mBlockingLayout = true;
4191 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4192 if (!AllowsL10n()) {
4193 return;
4196 if (mDocumentL10n) {
4197 nsAutoString href;
4198 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
4199 uint32_t remaining =
4200 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4201 if (remaining == 0) {
4202 if (mDocumentL10n->mBlockingLayout) {
4203 mDocumentL10n->mBlockingLayout = false;
4204 UnblockOnload(/* aFireSync = */ false);
4206 mDocumentL10n = nullptr;
4212 * This method should be called once the end of the l10n
4213 * resource container has been parsed.
4215 * In XUL this is the end of the first </linkset>,
4216 * In XHTML/HTML this is the end of </head>.
4218 * This milestone is used to allow for batch
4219 * localization context I/O and building done
4220 * once when all resources in the document have been
4221 * collected.
4223 void Document::OnL10nResourceContainerParsed() {
4224 // XXX: This is a scaffolding for where we might inject prefetch
4225 // in bug 1717241.
4228 void Document::OnParsingCompleted() {
4229 // Let's call it again, in case the resource
4230 // container has not been closed, and only
4231 // now we're closing the document.
4232 OnL10nResourceContainerParsed();
4234 if (mDocumentL10n) {
4235 mDocumentL10n->TriggerInitialTranslation();
4239 void Document::InitialTranslationCompleted(bool aL10nCached) {
4240 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4241 // This means we blocked the load event in LocalizationLinkAdded. It's
4242 // important that the load blocker removal here be async, because our caller
4243 // will notify the content sink after us, and we want the content sync's
4244 // work to happen before the load event fires.
4245 mDocumentL10n->mBlockingLayout = false;
4246 UnblockOnload(/* aFireSync = */ false);
4249 mL10nProtoElements.Clear();
4251 nsXULPrototypeDocument* proto = GetPrototype();
4252 if (proto) {
4253 proto->SetIsL10nCached(aL10nCached);
4257 bool Document::AllowsL10n() const {
4258 if (IsStaticDocument()) {
4259 // We don't allow l10n on static documents, because the nodes are already
4260 // cloned translated, and static docs don't get parsed so we never
4261 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4262 // forever.
4263 return false;
4265 bool allowed = false;
4266 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4267 return allowed;
4270 bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
4271 MOZ_ASSERT(NS_IsMainThread());
4273 return nsContentUtils::IsSystemCaller(aCx) ||
4274 StaticPrefs::dom_animations_api_core_enabled();
4277 bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
4278 MOZ_ASSERT(NS_IsMainThread());
4280 return aCallerType == dom::CallerType::System ||
4281 StaticPrefs::dom_animations_api_core_enabled();
4284 bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
4285 JSObject* /*unused*/
4287 MOZ_ASSERT(NS_IsMainThread());
4289 return nsContentUtils::IsSystemCaller(aCx) ||
4290 StaticPrefs::dom_animations_api_getAnimations_enabled();
4293 bool Document::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
4294 JSObject* /*unused*/
4296 MOZ_ASSERT(NS_IsMainThread());
4298 return nsContentUtils::IsSystemCaller(aCx) ||
4299 StaticPrefs::dom_animations_api_implicit_keyframes_enabled();
4302 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4303 JSObject* /*unused*/
4305 MOZ_ASSERT(NS_IsMainThread());
4307 return nsContentUtils::IsSystemCaller(aCx) ||
4308 StaticPrefs::dom_animations_api_timelines_enabled();
4311 DocumentTimeline* Document::Timeline() {
4312 if (!mDocumentTimeline) {
4313 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4316 return mDocumentTimeline;
4319 SVGSVGElement* Document::GetSVGRootElement() const {
4320 Element* root = GetRootElement();
4321 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4322 return nullptr;
4324 return static_cast<SVGSVGElement*>(root);
4327 /* Return true if the document is in the focused top-level window, and is an
4328 * ancestor of the focused DOMWindow. */
4329 bool Document::HasFocus(ErrorResult& rv) const {
4330 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4331 if (!fm) {
4332 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4333 return false;
4336 BrowsingContext* bc = GetBrowsingContext();
4337 if (!bc) {
4338 return false;
4341 if (!fm->IsInActiveWindow(bc)) {
4342 return false;
4345 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4348 void Document::GetDesignMode(nsAString& aDesignMode) {
4349 if (HasFlag(NODE_IS_EDITABLE)) {
4350 aDesignMode.AssignLiteral("on");
4351 } else {
4352 aDesignMode.AssignLiteral("off");
4356 void Document::SetDesignMode(const nsAString& aDesignMode,
4357 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4358 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4361 static void NotifyEditableStateChange(Document& aDoc) {
4362 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4363 nsMutationGuard g;
4364 #endif
4365 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4366 node = node->GetNextNode(&aDoc)) {
4367 if (auto* element = Element::FromNode(node)) {
4368 element->UpdateState(true);
4371 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4374 void Document::SetDesignMode(const nsAString& aDesignMode,
4375 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4376 ErrorResult& rv) {
4377 if (aSubjectPrincipal.isSome() &&
4378 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4379 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4380 return;
4382 bool editableMode = HasFlag(NODE_IS_EDITABLE);
4383 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4384 SetEditableFlag(!editableMode);
4385 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4386 // state of all descendant elements of it. Update that now.
4387 NotifyEditableStateChange(*this);
4388 rv = EditingStateChanged();
4392 nsCommandManager* Document::GetMidasCommandManager() {
4393 // check if we have it cached
4394 if (mMidasCommandManager) {
4395 return mMidasCommandManager;
4398 nsPIDOMWindowOuter* window = GetWindow();
4399 if (!window) {
4400 return nullptr;
4403 nsIDocShell* docshell = window->GetDocShell();
4404 if (!docshell) {
4405 return nullptr;
4408 mMidasCommandManager = docshell->GetCommandManager();
4409 return mMidasCommandManager;
4412 // static
4413 void Document::EnsureInitializeInternalCommandDataHashtable() {
4414 if (sInternalCommandDataHashtable) {
4415 return;
4417 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4418 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4419 // clang-format off
4420 sInternalCommandDataHashtable->InsertOrUpdate(
4421 u"bold"_ns,
4422 InternalCommandData(
4423 "cmd_bold",
4424 Command::FormatBold,
4425 ExecCommandParam::Ignore,
4426 StyleUpdatingCommand::GetInstance,
4427 CommandOnTextEditor::Disabled));
4428 sInternalCommandDataHashtable->InsertOrUpdate(
4429 u"italic"_ns,
4430 InternalCommandData(
4431 "cmd_italic",
4432 Command::FormatItalic,
4433 ExecCommandParam::Ignore,
4434 StyleUpdatingCommand::GetInstance,
4435 CommandOnTextEditor::Disabled));
4436 sInternalCommandDataHashtable->InsertOrUpdate(
4437 u"underline"_ns,
4438 InternalCommandData(
4439 "cmd_underline",
4440 Command::FormatUnderline,
4441 ExecCommandParam::Ignore,
4442 StyleUpdatingCommand::GetInstance,
4443 CommandOnTextEditor::Disabled));
4444 sInternalCommandDataHashtable->InsertOrUpdate(
4445 u"strikethrough"_ns,
4446 InternalCommandData(
4447 "cmd_strikethrough",
4448 Command::FormatStrikeThrough,
4449 ExecCommandParam::Ignore,
4450 StyleUpdatingCommand::GetInstance,
4451 CommandOnTextEditor::Disabled));
4452 sInternalCommandDataHashtable->InsertOrUpdate(
4453 u"subscript"_ns,
4454 InternalCommandData(
4455 "cmd_subscript",
4456 Command::FormatSubscript,
4457 ExecCommandParam::Ignore,
4458 StyleUpdatingCommand::GetInstance,
4459 CommandOnTextEditor::Disabled));
4460 sInternalCommandDataHashtable->InsertOrUpdate(
4461 u"superscript"_ns,
4462 InternalCommandData(
4463 "cmd_superscript",
4464 Command::FormatSuperscript,
4465 ExecCommandParam::Ignore,
4466 StyleUpdatingCommand::GetInstance,
4467 CommandOnTextEditor::Disabled));
4468 sInternalCommandDataHashtable->InsertOrUpdate(
4469 u"cut"_ns,
4470 InternalCommandData(
4471 "cmd_cut",
4472 Command::Cut,
4473 ExecCommandParam::Ignore,
4474 CutCommand::GetInstance,
4475 CommandOnTextEditor::Enabled));
4476 sInternalCommandDataHashtable->InsertOrUpdate(
4477 u"copy"_ns,
4478 InternalCommandData(
4479 "cmd_copy",
4480 Command::Copy,
4481 ExecCommandParam::Ignore,
4482 CopyCommand::GetInstance,
4483 CommandOnTextEditor::Enabled));
4484 sInternalCommandDataHashtable->InsertOrUpdate(
4485 u"paste"_ns,
4486 InternalCommandData(
4487 "cmd_paste",
4488 Command::Paste,
4489 ExecCommandParam::Ignore,
4490 PasteCommand::GetInstance,
4491 CommandOnTextEditor::Enabled));
4492 sInternalCommandDataHashtable->InsertOrUpdate(
4493 u"delete"_ns,
4494 InternalCommandData(
4495 "cmd_deleteCharBackward",
4496 Command::DeleteCharBackward,
4497 ExecCommandParam::Ignore,
4498 DeleteCommand::GetInstance,
4499 CommandOnTextEditor::Enabled));
4500 sInternalCommandDataHashtable->InsertOrUpdate(
4501 u"forwarddelete"_ns,
4502 InternalCommandData(
4503 "cmd_deleteCharForward",
4504 Command::DeleteCharForward,
4505 ExecCommandParam::Ignore,
4506 DeleteCommand::GetInstance,
4507 CommandOnTextEditor::Enabled));
4508 sInternalCommandDataHashtable->InsertOrUpdate(
4509 u"selectall"_ns,
4510 InternalCommandData(
4511 "cmd_selectAll",
4512 Command::SelectAll,
4513 ExecCommandParam::Ignore,
4514 SelectAllCommand::GetInstance,
4515 CommandOnTextEditor::Enabled));
4516 sInternalCommandDataHashtable->InsertOrUpdate(
4517 u"undo"_ns,
4518 InternalCommandData(
4519 "cmd_undo",
4520 Command::HistoryUndo,
4521 ExecCommandParam::Ignore,
4522 UndoCommand::GetInstance,
4523 CommandOnTextEditor::Enabled));
4524 sInternalCommandDataHashtable->InsertOrUpdate(
4525 u"redo"_ns,
4526 InternalCommandData(
4527 "cmd_redo",
4528 Command::HistoryRedo,
4529 ExecCommandParam::Ignore,
4530 RedoCommand::GetInstance,
4531 CommandOnTextEditor::Enabled));
4532 sInternalCommandDataHashtable->InsertOrUpdate(
4533 u"indent"_ns,
4534 InternalCommandData("cmd_indent",
4535 Command::FormatIndent,
4536 ExecCommandParam::Ignore,
4537 IndentCommand::GetInstance,
4538 CommandOnTextEditor::Disabled));
4539 sInternalCommandDataHashtable->InsertOrUpdate(
4540 u"outdent"_ns,
4541 InternalCommandData(
4542 "cmd_outdent",
4543 Command::FormatOutdent,
4544 ExecCommandParam::Ignore,
4545 OutdentCommand::GetInstance,
4546 CommandOnTextEditor::Disabled));
4547 sInternalCommandDataHashtable->InsertOrUpdate(
4548 u"backcolor"_ns,
4549 InternalCommandData(
4550 "cmd_highlight",
4551 Command::FormatBackColor,
4552 ExecCommandParam::String,
4553 HighlightColorStateCommand::GetInstance,
4554 CommandOnTextEditor::Disabled));
4555 sInternalCommandDataHashtable->InsertOrUpdate(
4556 u"hilitecolor"_ns,
4557 InternalCommandData(
4558 "cmd_highlight",
4559 Command::FormatBackColor,
4560 ExecCommandParam::String,
4561 HighlightColorStateCommand::GetInstance,
4562 CommandOnTextEditor::Disabled));
4563 sInternalCommandDataHashtable->InsertOrUpdate(
4564 u"forecolor"_ns,
4565 InternalCommandData(
4566 "cmd_fontColor",
4567 Command::FormatFontColor,
4568 ExecCommandParam::String,
4569 FontColorStateCommand::GetInstance,
4570 CommandOnTextEditor::Disabled));
4571 sInternalCommandDataHashtable->InsertOrUpdate(
4572 u"fontname"_ns,
4573 InternalCommandData(
4574 "cmd_fontFace",
4575 Command::FormatFontName,
4576 ExecCommandParam::String,
4577 FontFaceStateCommand::GetInstance,
4578 CommandOnTextEditor::Disabled));
4579 sInternalCommandDataHashtable->InsertOrUpdate(
4580 u"fontsize"_ns,
4581 InternalCommandData(
4582 "cmd_fontSize",
4583 Command::FormatFontSize,
4584 ExecCommandParam::String,
4585 FontSizeStateCommand::GetInstance,
4586 CommandOnTextEditor::Disabled));
4587 sInternalCommandDataHashtable->InsertOrUpdate(
4588 u"increasefontsize"_ns,
4589 InternalCommandData(
4590 "cmd_increaseFont",
4591 Command::FormatIncreaseFontSize,
4592 ExecCommandParam::Ignore,
4593 IncreaseFontSizeCommand::GetInstance,
4594 CommandOnTextEditor::Disabled));
4595 sInternalCommandDataHashtable->InsertOrUpdate(
4596 u"decreasefontsize"_ns,
4597 InternalCommandData(
4598 "cmd_decreaseFont",
4599 Command::FormatDecreaseFontSize,
4600 ExecCommandParam::Ignore,
4601 DecreaseFontSizeCommand::GetInstance,
4602 CommandOnTextEditor::Disabled));
4603 sInternalCommandDataHashtable->InsertOrUpdate(
4604 u"inserthorizontalrule"_ns,
4605 InternalCommandData(
4606 "cmd_insertHR",
4607 Command::InsertHorizontalRule,
4608 ExecCommandParam::Ignore,
4609 InsertTagCommand::GetInstance,
4610 CommandOnTextEditor::Disabled));
4611 sInternalCommandDataHashtable->InsertOrUpdate(
4612 u"createlink"_ns,
4613 InternalCommandData(
4614 "cmd_insertLinkNoUI",
4615 Command::InsertLink,
4616 ExecCommandParam::String,
4617 InsertTagCommand::GetInstance,
4618 CommandOnTextEditor::Disabled));
4619 sInternalCommandDataHashtable->InsertOrUpdate(
4620 u"insertimage"_ns,
4621 InternalCommandData(
4622 "cmd_insertImageNoUI",
4623 Command::InsertImage,
4624 ExecCommandParam::String,
4625 InsertTagCommand::GetInstance,
4626 CommandOnTextEditor::Disabled));
4627 sInternalCommandDataHashtable->InsertOrUpdate(
4628 u"inserthtml"_ns,
4629 InternalCommandData(
4630 "cmd_insertHTML",
4631 Command::InsertHTML,
4632 ExecCommandParam::String,
4633 InsertHTMLCommand::GetInstance,
4634 // TODO: Chromium inserts text content of the document fragment
4635 // created from the param.
4636 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4637 CommandOnTextEditor::Disabled));
4638 sInternalCommandDataHashtable->InsertOrUpdate(
4639 u"inserttext"_ns,
4640 InternalCommandData(
4641 "cmd_insertText",
4642 Command::InsertText,
4643 ExecCommandParam::String,
4644 InsertPlaintextCommand::GetInstance,
4645 CommandOnTextEditor::Enabled));
4646 sInternalCommandDataHashtable->InsertOrUpdate(
4647 u"gethtml"_ns,
4648 InternalCommandData(
4649 "cmd_getContents",
4650 Command::GetHTML,
4651 ExecCommandParam::Ignore,
4652 nullptr, // Not defined in EditorCommands.h
4653 // getHTML command is not supported by Chromium, and we return HTML
4654 // source code at selected range. So, let's return selected text
4655 // when `<input>` or `<textarea>` has focus.
4656 CommandOnTextEditor::Enabled));
4657 sInternalCommandDataHashtable->InsertOrUpdate(
4658 u"justifyleft"_ns,
4659 InternalCommandData(
4660 "cmd_align",
4661 Command::FormatJustifyLeft,
4662 ExecCommandParam::Ignore, // Will be set to "left"
4663 AlignCommand::GetInstance,
4664 CommandOnTextEditor::Disabled));
4665 sInternalCommandDataHashtable->InsertOrUpdate(
4666 u"justifyright"_ns,
4667 InternalCommandData(
4668 "cmd_align",
4669 Command::FormatJustifyRight,
4670 ExecCommandParam::Ignore, // Will be set to "right"
4671 AlignCommand::GetInstance,
4672 CommandOnTextEditor::Disabled));
4673 sInternalCommandDataHashtable->InsertOrUpdate(
4674 u"justifycenter"_ns,
4675 InternalCommandData(
4676 "cmd_align",
4677 Command::FormatJustifyCenter,
4678 ExecCommandParam::Ignore, // Will be set to "center"
4679 AlignCommand::GetInstance,
4680 CommandOnTextEditor::Disabled));
4681 sInternalCommandDataHashtable->InsertOrUpdate(
4682 u"justifyfull"_ns,
4683 InternalCommandData(
4684 "cmd_align",
4685 Command::FormatJustifyFull,
4686 ExecCommandParam::Ignore, // Will be set to "justify"
4687 AlignCommand::GetInstance,
4688 CommandOnTextEditor::Disabled));
4689 sInternalCommandDataHashtable->InsertOrUpdate(
4690 u"removeformat"_ns,
4691 InternalCommandData(
4692 "cmd_removeStyles",
4693 Command::FormatRemove,
4694 ExecCommandParam::Ignore,
4695 RemoveStylesCommand::GetInstance,
4696 CommandOnTextEditor::Disabled));
4697 sInternalCommandDataHashtable->InsertOrUpdate(
4698 u"unlink"_ns,
4699 InternalCommandData(
4700 "cmd_removeLinks",
4701 Command::FormatRemoveLink,
4702 ExecCommandParam::Ignore,
4703 StyleUpdatingCommand::GetInstance,
4704 CommandOnTextEditor::Disabled));
4705 sInternalCommandDataHashtable->InsertOrUpdate(
4706 u"insertorderedlist"_ns,
4707 InternalCommandData(
4708 "cmd_ol",
4709 Command::InsertOrderedList,
4710 ExecCommandParam::Ignore,
4711 ListCommand::GetInstance,
4712 CommandOnTextEditor::Disabled));
4713 sInternalCommandDataHashtable->InsertOrUpdate(
4714 u"insertunorderedlist"_ns,
4715 InternalCommandData(
4716 "cmd_ul",
4717 Command::InsertUnorderedList,
4718 ExecCommandParam::Ignore,
4719 ListCommand::GetInstance,
4720 CommandOnTextEditor::Disabled));
4721 sInternalCommandDataHashtable->InsertOrUpdate(
4722 u"insertparagraph"_ns,
4723 InternalCommandData(
4724 "cmd_insertParagraph",
4725 Command::InsertParagraph,
4726 ExecCommandParam::Ignore,
4727 InsertParagraphCommand::GetInstance,
4728 CommandOnTextEditor::Enabled));
4729 sInternalCommandDataHashtable->InsertOrUpdate(
4730 u"insertlinebreak"_ns,
4731 InternalCommandData(
4732 "cmd_insertLineBreak",
4733 Command::InsertLineBreak,
4734 ExecCommandParam::Ignore,
4735 InsertLineBreakCommand::GetInstance,
4736 CommandOnTextEditor::Enabled));
4737 sInternalCommandDataHashtable->InsertOrUpdate(
4738 u"formatblock"_ns,
4739 InternalCommandData(
4740 "cmd_paragraphState",
4741 Command::FormatBlock,
4742 ExecCommandParam::String,
4743 ParagraphStateCommand::GetInstance,
4744 CommandOnTextEditor::Disabled));
4745 sInternalCommandDataHashtable->InsertOrUpdate(
4746 u"heading"_ns,
4747 InternalCommandData(
4748 "cmd_paragraphState",
4749 Command::FormatBlock,
4750 ExecCommandParam::String,
4751 ParagraphStateCommand::GetInstance,
4752 CommandOnTextEditor::Disabled));
4753 sInternalCommandDataHashtable->InsertOrUpdate(
4754 u"styleWithCSS"_ns,
4755 InternalCommandData(
4756 "cmd_setDocumentUseCSS",
4757 Command::SetDocumentUseCSS,
4758 ExecCommandParam::Boolean,
4759 SetDocumentStateCommand::GetInstance,
4760 CommandOnTextEditor::FallThrough));
4761 sInternalCommandDataHashtable->InsertOrUpdate(
4762 u"usecss"_ns, // Legacy command
4763 InternalCommandData(
4764 "cmd_setDocumentUseCSS",
4765 Command::SetDocumentUseCSS,
4766 ExecCommandParam::InvertedBoolean,
4767 SetDocumentStateCommand::GetInstance,
4768 CommandOnTextEditor::FallThrough));
4769 sInternalCommandDataHashtable->InsertOrUpdate(
4770 u"contentReadOnly"_ns,
4771 InternalCommandData(
4772 "cmd_setDocumentReadOnly",
4773 Command::SetDocumentReadOnly,
4774 ExecCommandParam::Boolean,
4775 SetDocumentStateCommand::GetInstance,
4776 CommandOnTextEditor::Enabled));
4777 sInternalCommandDataHashtable->InsertOrUpdate(
4778 u"readonly"_ns, // Legacy command
4779 InternalCommandData(
4780 "cmd_setDocumentReadOnly",
4781 Command::SetDocumentReadOnly,
4782 ExecCommandParam::InvertedBoolean,
4783 SetDocumentStateCommand::GetInstance,
4784 CommandOnTextEditor::Enabled));
4785 sInternalCommandDataHashtable->InsertOrUpdate(
4786 u"insertBrOnReturn"_ns,
4787 InternalCommandData(
4788 "cmd_insertBrOnReturn",
4789 Command::SetDocumentInsertBROnEnterKeyPress,
4790 ExecCommandParam::Boolean,
4791 SetDocumentStateCommand::GetInstance,
4792 CommandOnTextEditor::FallThrough));
4793 sInternalCommandDataHashtable->InsertOrUpdate(
4794 u"defaultParagraphSeparator"_ns,
4795 InternalCommandData(
4796 "cmd_defaultParagraphSeparator",
4797 Command::SetDocumentDefaultParagraphSeparator,
4798 ExecCommandParam::String,
4799 SetDocumentStateCommand::GetInstance,
4800 CommandOnTextEditor::FallThrough));
4801 sInternalCommandDataHashtable->InsertOrUpdate(
4802 u"enableObjectResizing"_ns,
4803 InternalCommandData(
4804 "cmd_enableObjectResizing",
4805 Command::ToggleObjectResizers,
4806 ExecCommandParam::Boolean,
4807 SetDocumentStateCommand::GetInstance,
4808 CommandOnTextEditor::FallThrough));
4809 sInternalCommandDataHashtable->InsertOrUpdate(
4810 u"enableInlineTableEditing"_ns,
4811 InternalCommandData(
4812 "cmd_enableInlineTableEditing",
4813 Command::ToggleInlineTableEditor,
4814 ExecCommandParam::Boolean,
4815 SetDocumentStateCommand::GetInstance,
4816 CommandOnTextEditor::FallThrough));
4817 sInternalCommandDataHashtable->InsertOrUpdate(
4818 u"enableAbsolutePositionEditing"_ns,
4819 InternalCommandData(
4820 "cmd_enableAbsolutePositionEditing",
4821 Command::ToggleAbsolutePositionEditor,
4822 ExecCommandParam::Boolean,
4823 SetDocumentStateCommand::GetInstance,
4824 CommandOnTextEditor::FallThrough));
4825 #if 0
4826 // with empty string
4827 sInternalCommandDataHashtable->InsertOrUpdate(
4828 u"justifynone"_ns,
4829 InternalCommandData(
4830 "cmd_align",
4831 Command::Undefined,
4832 ExecCommandParam::Ignore,
4833 nullptr,
4834 CommandOnTextEditor::Disabled)); // Not implemented yet.
4835 // REQUIRED SPECIAL REVIEW special review
4836 sInternalCommandDataHashtable->InsertOrUpdate(
4837 u"saveas"_ns,
4838 InternalCommandData(
4839 "cmd_saveAs",
4840 Command::Undefined,
4841 ExecCommandParam::Boolean,
4842 nullptr,
4843 CommandOnTextEditor::FallThrough)); // Not implemented yet.
4844 // REQUIRED SPECIAL REVIEW special review
4845 sInternalCommandDataHashtable->InsertOrUpdate(
4846 u"print"_ns,
4847 InternalCommandData(
4848 "cmd_print",
4849 Command::Undefined,
4850 ExecCommandParam::Boolean,
4851 nullptr,
4852 CommandOnTextEditor::FallThrough)); // Not implemented yet.
4853 #endif // #if 0
4854 // clang-format on
4857 Document::InternalCommandData Document::ConvertToInternalCommand(
4858 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
4859 nsAString* aAdjustedValue /* = nullptr */) {
4860 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
4861 EnsureInitializeInternalCommandDataHashtable();
4862 InternalCommandData commandData;
4863 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
4864 return InternalCommandData();
4866 if (!aAdjustedValue) {
4867 // No further work to do
4868 return commandData;
4870 switch (commandData.mExecCommandParam) {
4871 case ExecCommandParam::Ignore:
4872 // Just have to copy it, no checking
4873 switch (commandData.mCommand) {
4874 case Command::FormatJustifyLeft:
4875 aAdjustedValue->AssignLiteral("left");
4876 break;
4877 case Command::FormatJustifyRight:
4878 aAdjustedValue->AssignLiteral("right");
4879 break;
4880 case Command::FormatJustifyCenter:
4881 aAdjustedValue->AssignLiteral("center");
4882 break;
4883 case Command::FormatJustifyFull:
4884 aAdjustedValue->AssignLiteral("justify");
4885 break;
4886 default:
4887 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
4888 EditorCommandParamType::None);
4889 break;
4891 return commandData;
4893 case ExecCommandParam::Boolean:
4894 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
4895 EditorCommandParamType::Bool));
4896 // If this is a boolean value and it's not explicitly false (e.g. no
4897 // value). We default to "true" (see bug 301490).
4898 if (!aValue.LowerCaseEqualsLiteral("false")) {
4899 aAdjustedValue->AssignLiteral("true");
4900 } else {
4901 aAdjustedValue->AssignLiteral("false");
4903 return commandData;
4905 case ExecCommandParam::InvertedBoolean:
4906 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
4907 EditorCommandParamType::Bool));
4908 // For old backwards commands we invert the check.
4909 if (aValue.LowerCaseEqualsLiteral("false")) {
4910 aAdjustedValue->AssignLiteral("true");
4911 } else {
4912 aAdjustedValue->AssignLiteral("false");
4914 return commandData;
4916 case ExecCommandParam::String:
4917 MOZ_ASSERT(!!(
4918 EditorCommand::GetParamType(commandData.mCommand) &
4919 (EditorCommandParamType::String | EditorCommandParamType::CString)));
4920 switch (commandData.mCommand) {
4921 case Command::FormatBlock: {
4922 const char16_t* start = aValue.BeginReading();
4923 const char16_t* end = aValue.EndReading();
4924 if (start != end && *start == '<' && *(end - 1) == '>') {
4925 ++start;
4926 --end;
4928 // XXX Should we reorder this array with actual usage?
4929 static const nsStaticAtom* kFormattableBlockTags[] = {
4930 // clang-format off
4931 nsGkAtoms::address,
4932 nsGkAtoms::blockquote,
4933 nsGkAtoms::dd,
4934 nsGkAtoms::div,
4935 nsGkAtoms::dl,
4936 nsGkAtoms::dt,
4937 nsGkAtoms::h1,
4938 nsGkAtoms::h2,
4939 nsGkAtoms::h3,
4940 nsGkAtoms::h4,
4941 nsGkAtoms::h5,
4942 nsGkAtoms::h6,
4943 nsGkAtoms::p,
4944 nsGkAtoms::pre,
4945 // clang-format on
4947 nsAutoString value(nsDependentSubstring(start, end));
4948 ToLowerCase(value);
4949 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
4950 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
4951 if (valueAtom == kTag) {
4952 kTag->ToString(*aAdjustedValue);
4953 return commandData;
4956 return InternalCommandData();
4958 case Command::FormatFontSize: {
4959 // Per editing spec as of April 23, 2012, we need to reject the value
4960 // if it's not a valid floating-point number surrounded by optional
4961 // whitespace. Otherwise, we parse it as a legacy font size. For
4962 // now, we just parse as a legacy font size regardless (matching
4963 // WebKit) -- bug 747879.
4964 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
4965 if (!size) {
4966 return InternalCommandData();
4968 MOZ_ASSERT(aAdjustedValue->IsEmpty());
4969 aAdjustedValue->AppendInt(size);
4970 return commandData;
4972 case Command::InsertImage:
4973 case Command::InsertLink:
4974 if (aValue.IsEmpty()) {
4975 // Invalid value, return false
4976 return InternalCommandData();
4978 aAdjustedValue->Assign(aValue);
4979 return commandData;
4980 case Command::SetDocumentDefaultParagraphSeparator:
4981 if (!aValue.LowerCaseEqualsLiteral("div") &&
4982 !aValue.LowerCaseEqualsLiteral("p") &&
4983 !aValue.LowerCaseEqualsLiteral("br")) {
4984 // Invalid value
4985 return InternalCommandData();
4987 aAdjustedValue->Assign(aValue);
4988 return commandData;
4989 default:
4990 aAdjustedValue->Assign(aValue);
4991 return commandData;
4994 default:
4995 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
4996 return InternalCommandData();
5000 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5001 nsPresContext* aPresContext, const InternalCommandData& aCommandData)
5002 : mCommandData(aCommandData) {
5003 // Consider context of command handling which is automatically resolved
5004 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5005 // The order is:
5006 // 1. TextEditor if there is an active element and it has TextEditor like
5007 // <input type="text"> or <textarea>.
5008 // 2. HTMLEditor for the document, if there is.
5009 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5010 if (aPresContext) {
5011 if (aCommandData.IsCutOrCopyCommand()) {
5012 // Note that we used to use DocShell to handle `cut` and `copy` command
5013 // for dispatching corresponding events for making possible web apps to
5014 // implement their own editor without editable elements but supports
5015 // standard shortcut keys, etc. In this case, we prefer to use active
5016 // element's editor to keep same behavior.
5017 mActiveEditor = nsContentUtils::GetActiveEditor(aPresContext);
5018 } else {
5019 mActiveEditor = nsContentUtils::GetActiveEditor(aPresContext);
5020 mHTMLEditor = nsContentUtils::GetHTMLEditor(aPresContext);
5021 if (!mActiveEditor) {
5022 mActiveEditor = mHTMLEditor;
5027 // Then, retrieve editor command class instance which should handle it
5028 // and can handle it now.
5029 if (!mActiveEditor) {
5030 // If the command is available without editor, we should redirect the
5031 // command to focused descendant with DocShell.
5032 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5033 mDoNothing = true;
5034 return;
5036 return;
5039 // Otherwise, we should use EditorCommand instance (which is singleton
5040 // instance) when it's enabled.
5041 mEditorCommand = aCommandData.mGetEditorCommandFunc
5042 ? aCommandData.mGetEditorCommandFunc()
5043 : nullptr;
5044 if (!mEditorCommand) {
5045 mDoNothing = true;
5046 mActiveEditor = nullptr;
5047 mHTMLEditor = nullptr;
5048 return;
5051 if (IsCommandEnabled()) {
5052 return;
5055 // If the EditorCommand instance is disabled, we should do nothing if
5056 // the command requires an editor.
5057 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5058 // Do nothing if editor specific commands is disabled (bug 760052).
5059 mDoNothing = true;
5060 return;
5063 // Otherwise, we should redirect it to focused descendant with DocShell.
5064 mEditorCommand = nullptr;
5065 mActiveEditor = nullptr;
5066 mHTMLEditor = nullptr;
5069 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5070 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5071 switch (mCommandData.mCommandOnTextEditor) {
5072 case CommandOnTextEditor::Enabled:
5073 return mActiveEditor;
5074 case CommandOnTextEditor::Disabled:
5075 return mActiveEditor && mActiveEditor->IsTextEditor()
5076 ? nullptr
5077 : mActiveEditor.get();
5078 case CommandOnTextEditor::FallThrough:
5079 return mHTMLEditor;
5081 return nullptr;
5084 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5085 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5086 // Make sure frames are up to date, since that can affect whether
5087 // we're editable.
5088 doc->FlushPendingNotifications(FlushType::Frames);
5090 EditorBase* targetEditor = GetTargetEditor();
5091 if (targetEditor && targetEditor->IsTextEditor()) {
5092 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5093 // "readonly" too.
5094 return !targetEditor->IsReadonly();
5096 return aDocument->IsEditingOn();
5099 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5100 EditorBase* targetEditor = GetTargetEditor();
5101 if (!targetEditor) {
5102 return false;
5104 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5105 return MOZ_KnownLive(mEditorCommand)
5106 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5109 nsresult Document::AutoEditorCommandTarget::DoCommand(
5110 nsIPrincipal* aPrincipal) const {
5111 MOZ_ASSERT(!DoNothing());
5112 MOZ_ASSERT(mEditorCommand);
5113 EditorBase* targetEditor = GetTargetEditor();
5114 if (!targetEditor) {
5115 return NS_SUCCESS_DOM_NO_OPERATION;
5117 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5118 return MOZ_KnownLive(mEditorCommand)
5119 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5120 aPrincipal);
5123 template <typename ParamType>
5124 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5125 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5126 MOZ_ASSERT(!DoNothing());
5127 MOZ_ASSERT(mEditorCommand);
5128 EditorBase* targetEditor = GetTargetEditor();
5129 if (!targetEditor) {
5130 return NS_SUCCESS_DOM_NO_OPERATION;
5132 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5133 return MOZ_KnownLive(mEditorCommand)
5134 ->DoCommandParam(mCommandData.mCommand, aParam,
5135 MOZ_KnownLive(*targetEditor), aPrincipal);
5138 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5139 nsCommandParams& aParams) const {
5140 MOZ_ASSERT(mEditorCommand);
5141 EditorBase* targetEditor = GetTargetEditor();
5142 if (!targetEditor) {
5143 return NS_OK;
5145 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5146 return MOZ_KnownLive(mEditorCommand)
5147 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5148 MOZ_KnownLive(targetEditor), nullptr);
5151 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5152 const nsAString& aValue,
5153 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5154 // Only allow on HTML documents.
5155 if (!IsHTMLOrXHTML()) {
5156 aRv.ThrowInvalidStateError(
5157 "execCommand is only supported on HTML documents");
5158 return false;
5160 // Otherwise, don't throw exception for compatibility with Chrome.
5162 // if they are requesting UI from us, let's fail since we have no UI
5163 if (aShowUI) {
5164 return false;
5167 // If we're running an execCommand, we should just return false.
5168 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5169 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5170 mIsRunningExecCommand) {
5171 return false;
5174 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5175 // this might add some ugly JS dependencies?
5177 nsAutoString adjustedValue;
5178 InternalCommandData commandData =
5179 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5180 switch (commandData.mCommand) {
5181 case Command::DoNothing:
5182 // "gethtml" command is a command to retrieve a string value, not executing
5183 // anything and not enough the `bool` value of `execCommand`. So, at here,
5184 // we do nothing for "gethtml" command.
5185 case Command::GetHTML:
5186 return false;
5187 case Command::FormatIncreaseFontSize:
5188 SetUseCounter(eUseCounter_custom_DocumentExecCommandIncreaseFontSize);
5189 break;
5190 case Command::FormatDecreaseFontSize:
5191 SetUseCounter(eUseCounter_custom_DocumentExecCommandDecreaseFontSize);
5192 break;
5193 case Command::FormatBlock:
5194 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5195 SetUseCounter(eUseCounter_custom_DocumentExecCommandHeading);
5197 break;
5198 case Command::SetDocumentReadOnly:
5199 SetUseCounter(aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5200 ? eUseCounter_custom_DocumentExecCommandContentReadOnly
5201 : eUseCounter_custom_DocumentExecCommandReadOnly);
5202 break;
5203 default:
5204 break;
5207 // Do security check first.
5208 if (commandData.IsCutOrCopyCommand()) {
5209 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5210 // We have rejected the event due to it not being performed in an
5211 // input-driven context therefore, we report the error to the console.
5212 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5213 this, nsContentUtils::eDOM_PROPERTIES,
5214 "ExecCommandCutCopyDeniedNotInputDriven");
5215 return false;
5217 } else if (commandData.IsPasteCommand()) {
5218 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5219 nsGkAtoms::clipboardRead)) {
5220 return false;
5224 // Next, consider context of command handling which is automatically resolved
5225 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5226 RefPtr<nsPresContext> presContext = GetPresContext();
5227 AutoEditorCommandTarget editCommandTarget(presContext, commandData);
5228 if (commandData.IsAvailableOnlyWhenEditable() &&
5229 !editCommandTarget.IsEditable(this)) {
5230 return false;
5233 if (editCommandTarget.DoNothing()) {
5234 return false;
5237 AutoRunningExecCommandMarker markRunningExecCommand(*this);
5239 // If we cannot use EditorCommand instance directly, we need to handle the
5240 // command with traditional path (i.e., with DocShell or nsCommandManager).
5241 if (!editCommandTarget.IsEditor()) {
5242 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5244 // Special case clipboard write commands like Command::Cut and
5245 // Command::Copy. For such commands, we need the behaviour from
5246 // nsWindowRoot::GetControllers() which is to look at the focused element,
5247 // and defer to a focused textbox's controller. The code past taken by
5248 // other commands in ExecCommand() always uses the window directly, rather
5249 // than deferring to the textbox, which is desireable for most editor
5250 // commands, but not these commands (as those should allow copying out of
5251 // embedded editors). This behaviour is invoked if we call DoCommand()
5252 // directly on the docShell.
5253 // XXX This means that we allow web app to pick up selected content in
5254 // descendant document and write it into the clipboard when a
5255 // descendant document has focus. However, Chromium does not allow
5256 // this and this seems that it's not good behavior from point of view
5257 // of security. We should treat this issue in another bug.
5258 if (commandData.IsCutOrCopyCommand()) {
5259 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5260 if (!docShell) {
5261 return false;
5263 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5264 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5265 return false;
5267 return NS_SUCCEEDED(rv);
5270 // Otherwise (currently, only clipboard read commands like Command::Paste),
5271 // we don't need to redirect the command to focused subdocument.
5272 // Therefore, we should handle it with nsCommandManager as used to be.
5273 // It may dispatch only preceding event of editing on non-editable element
5274 // to make web apps possible to handle standard shortcut key, etc in
5275 // their own editor.
5276 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5277 if (!commandManager) {
5278 return false;
5281 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5282 if (!window) {
5283 return false;
5286 // Return false for disabled commands (bug 760052)
5287 if (!commandManager->IsCommandEnabled(
5288 nsDependentCString(commandData.mXULCommandName), window)) {
5289 return false;
5292 MOZ_ASSERT(commandData.IsPasteCommand() ||
5293 commandData.mCommand == Command::SelectAll);
5294 nsresult rv =
5295 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5296 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5299 // Now, our target is fixed to the editor. So, we can use EditorCommand
5300 // in EditorCommandTarget directly.
5302 EditorCommandParamType paramType =
5303 EditorCommand::GetParamType(commandData.mCommand);
5305 // If we don't have meaningful parameter or the EditorCommand does not
5306 // require additional parameter, we can use `DoCommand()`.
5307 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5308 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5309 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5310 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5313 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5314 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5315 // a `bool` value.
5316 if (!!(paramType & EditorCommandParamType::Bool)) {
5317 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5318 adjustedValue.EqualsLiteral("false"));
5319 nsresult rv = editCommandTarget.DoCommandParam(
5320 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5321 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5324 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5325 // in this case. However, `paramType` may contain both `String` and
5326 // `CString` but in such case, we should use `DoCommandParam()` which
5327 // takes `nsAString`. So, we should check whether `paramType` contains
5328 // `String` or not first.
5329 if (!!(paramType & EditorCommandParamType::String)) {
5330 MOZ_ASSERT(!adjustedValue.IsVoid());
5331 nsresult rv =
5332 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5333 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5336 // Finally, `paramType` should have `CString`. We should use
5337 // `DoCommandParam()` which takes `nsACString`.
5338 if (!!(paramType & EditorCommandParamType::CString)) {
5339 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5340 MOZ_ASSERT(!utf8Value.IsVoid());
5341 nsresult rv =
5342 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5343 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5346 MOZ_ASSERT_UNREACHABLE(
5347 "Not yet implemented to handle new EditorCommandParamType");
5348 return false;
5351 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5352 nsIPrincipal& aSubjectPrincipal,
5353 ErrorResult& aRv) {
5354 // Only allow on HTML documents.
5355 if (!IsHTMLOrXHTML()) {
5356 aRv.ThrowInvalidStateError(
5357 "queryCommandEnabled is only supported on HTML documents");
5358 return false;
5360 // Otherwise, don't throw exception for compatibility with Chrome.
5362 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5363 switch (commandData.mCommand) {
5364 case Command::DoNothing:
5365 return false;
5366 case Command::FormatIncreaseFontSize:
5367 SetUseCounter(
5368 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledIncreaseFontSize);
5369 break;
5370 case Command::FormatDecreaseFontSize:
5371 SetUseCounter(
5372 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledDecreaseFontSize);
5373 break;
5374 case Command::GetHTML:
5375 SetUseCounter(
5376 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledGetHTML);
5377 break;
5378 case Command::FormatBlock:
5379 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5380 SetUseCounter(
5381 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledHeading);
5383 break;
5384 case Command::SetDocumentReadOnly:
5385 SetUseCounter(
5386 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5387 ? eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly
5388 : eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledReadOnly);
5389 break;
5390 case Command::SetDocumentInsertBROnEnterKeyPress:
5391 SetUseCounter(
5392 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5393 break;
5394 default:
5395 break;
5398 // cut & copy are always allowed
5399 if (commandData.IsCutOrCopyCommand()) {
5400 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5403 // Report false for restricted commands
5404 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5405 return false;
5408 RefPtr<nsPresContext> presContext = GetPresContext();
5409 AutoEditorCommandTarget editCommandTarget(presContext, commandData);
5410 if (commandData.IsAvailableOnlyWhenEditable() &&
5411 !editCommandTarget.IsEditable(this)) {
5412 return false;
5415 if (editCommandTarget.IsEditor()) {
5416 return editCommandTarget.IsCommandEnabled();
5419 // get command manager and dispatch command to our window if it's acceptable
5420 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5421 if (!commandManager) {
5422 return false;
5425 nsPIDOMWindowOuter* window = GetWindow();
5426 if (!window) {
5427 return false;
5430 return commandManager->IsCommandEnabled(
5431 nsDependentCString(commandData.mXULCommandName), window);
5434 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5435 ErrorResult& aRv) {
5436 // Only allow on HTML documents.
5437 if (!IsHTMLOrXHTML()) {
5438 aRv.ThrowInvalidStateError(
5439 "queryCommandIndeterm is only supported on HTML documents");
5440 return false;
5442 // Otherwise, don't throw exception for compatibility with Chrome.
5444 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5445 if (commandData.mCommand == Command::DoNothing) {
5446 return false;
5449 RefPtr<nsPresContext> presContext = GetPresContext();
5450 AutoEditorCommandTarget editCommandTarget(presContext, commandData);
5451 if (commandData.IsAvailableOnlyWhenEditable() &&
5452 !editCommandTarget.IsEditable(this)) {
5453 return false;
5455 RefPtr<nsCommandParams> params = new nsCommandParams();
5456 if (editCommandTarget.IsEditor()) {
5457 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5458 return false;
5460 } else {
5461 // get command manager and dispatch command to our window if it's acceptable
5462 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5463 if (!commandManager) {
5464 return false;
5467 nsPIDOMWindowOuter* window = GetWindow();
5468 if (!window) {
5469 return false;
5472 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5473 window, params))) {
5474 return false;
5478 // If command does not have a state_mixed value, this call fails and sets
5479 // retval to false. This is fine -- we want to return false in that case
5480 // anyway (bug 738385), so we just don't throw regardless.
5481 return params->GetBool("state_mixed");
5484 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5485 ErrorResult& aRv) {
5486 // Only allow on HTML documents.
5487 if (!IsHTMLOrXHTML()) {
5488 aRv.ThrowInvalidStateError(
5489 "queryCommandState is only supported on HTML documents");
5490 return false;
5492 // Otherwise, don't throw exception for compatibility with Chrome.
5494 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5495 switch (commandData.mCommand) {
5496 case Command::DoNothing:
5497 return false;
5498 case Command::GetHTML:
5499 SetUseCounter(eUseCounter_custom_DocumentQueryCommandStateOrValueGetHTML);
5500 break;
5501 case Command::FormatBlock:
5502 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5503 SetUseCounter(
5504 eUseCounter_custom_DocumentQueryCommandStateOrValueHeading);
5506 break;
5507 case Command::SetDocumentReadOnly:
5508 SetUseCounter(
5509 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5510 ? eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly
5511 : eUseCounter_custom_DocumentQueryCommandStateOrValueReadOnly);
5512 break;
5513 case Command::SetDocumentInsertBROnEnterKeyPress:
5514 SetUseCounter(
5515 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5516 break;
5517 default:
5518 break;
5521 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5522 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5523 // return false always.
5524 return false;
5527 RefPtr<nsPresContext> presContext = GetPresContext();
5528 AutoEditorCommandTarget editCommandTarget(presContext, commandData);
5529 if (commandData.IsAvailableOnlyWhenEditable() &&
5530 !editCommandTarget.IsEditable(this)) {
5531 return false;
5533 RefPtr<nsCommandParams> params = new nsCommandParams();
5534 if (editCommandTarget.IsEditor()) {
5535 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5536 return false;
5538 } else {
5539 // get command manager and dispatch command to our window if it's acceptable
5540 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5541 if (!commandManager) {
5542 return false;
5545 nsPIDOMWindowOuter* window = GetWindow();
5546 if (!window) {
5547 return false;
5550 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5551 window, params))) {
5552 return false;
5556 // handle alignment as a special case (possibly other commands too?)
5557 // Alignment is special because the external api is individual
5558 // commands but internally we use cmd_align with different
5559 // parameters. When getting the state of this command, we need to
5560 // return the boolean for this particular alignment rather than the
5561 // string of 'which alignment is this?'
5562 switch (commandData.mCommand) {
5563 case Command::FormatJustifyLeft: {
5564 nsAutoCString currentValue;
5565 nsresult rv = params->GetCString("state_attribute", currentValue);
5566 if (NS_FAILED(rv)) {
5567 return false;
5569 return currentValue.EqualsLiteral("left");
5571 case Command::FormatJustifyRight: {
5572 nsAutoCString currentValue;
5573 nsresult rv = params->GetCString("state_attribute", currentValue);
5574 if (NS_FAILED(rv)) {
5575 return false;
5577 return currentValue.EqualsLiteral("right");
5579 case Command::FormatJustifyCenter: {
5580 nsAutoCString currentValue;
5581 nsresult rv = params->GetCString("state_attribute", currentValue);
5582 if (NS_FAILED(rv)) {
5583 return false;
5585 return currentValue.EqualsLiteral("center");
5587 case Command::FormatJustifyFull: {
5588 nsAutoCString currentValue;
5589 nsresult rv = params->GetCString("state_attribute", currentValue);
5590 if (NS_FAILED(rv)) {
5591 return false;
5593 return currentValue.EqualsLiteral("justify");
5595 default:
5596 break;
5599 // If command does not have a state_all value, this call fails and sets
5600 // retval to false. This is fine -- we want to return false in that case
5601 // anyway (bug 738385), so we just succeed and return false regardless.
5602 return params->GetBool("state_all");
5605 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5606 CallerType aCallerType, ErrorResult& aRv) {
5607 // Only allow on HTML documents.
5608 if (!IsHTMLOrXHTML()) {
5609 aRv.ThrowInvalidStateError(
5610 "queryCommandSupported is only supported on HTML documents");
5611 return false;
5613 // Otherwise, don't throw exception for compatibility with Chrome.
5615 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5616 switch (commandData.mCommand) {
5617 case Command::DoNothing:
5618 return false;
5619 case Command::FormatIncreaseFontSize:
5620 SetUseCounter(
5621 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledIncreaseFontSize);
5622 break;
5623 case Command::FormatDecreaseFontSize:
5624 SetUseCounter(
5625 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledDecreaseFontSize);
5626 break;
5627 case Command::GetHTML:
5628 SetUseCounter(
5629 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledGetHTML);
5630 break;
5631 case Command::FormatBlock:
5632 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5633 SetUseCounter(
5634 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledHeading);
5636 break;
5637 case Command::SetDocumentReadOnly:
5638 SetUseCounter(
5639 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5640 ? eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly
5641 : eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledReadOnly);
5642 break;
5643 case Command::SetDocumentInsertBROnEnterKeyPress:
5644 SetUseCounter(
5645 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5646 break;
5647 default:
5648 break;
5651 // Gecko technically supports all the clipboard commands including
5652 // cut/copy/paste, but non-privileged content will be unable to call
5653 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5654 // may also be disallowed to be called from non-privileged content.
5655 // For that reason, we report the support status of corresponding
5656 // command accordingly.
5657 if (aCallerType != CallerType::System) {
5658 if (commandData.IsPasteCommand()) {
5659 return false;
5661 if (commandData.IsCutOrCopyCommand() &&
5662 !StaticPrefs::dom_allow_cut_copy()) {
5663 // XXXbz should we worry about correctly reporting "true" in the
5664 // "restricted, but we're an addon with clipboardWrite permissions" case?
5665 // See also nsContentUtils::IsCutCopyAllowed.
5666 return false;
5670 // aHTMLCommandName is supported if it can be converted to a Midas command
5671 return true;
5674 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5675 nsAString& aValue, ErrorResult& aRv) {
5676 aValue.Truncate();
5678 // Only allow on HTML documents.
5679 if (!IsHTMLOrXHTML()) {
5680 aRv.ThrowInvalidStateError(
5681 "queryCommandValue is only supported on HTML documents");
5682 return;
5684 // Otherwise, don't throw exception for compatibility with Chrome.
5686 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5687 switch (commandData.mCommand) {
5688 case Command::DoNothing:
5689 // Return empty string
5690 return;
5691 case Command::GetHTML:
5692 SetUseCounter(eUseCounter_custom_DocumentQueryCommandStateOrValueGetHTML);
5693 break;
5694 case Command::FormatBlock:
5695 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5696 SetUseCounter(
5697 eUseCounter_custom_DocumentQueryCommandStateOrValueHeading);
5699 break;
5700 case Command::SetDocumentReadOnly:
5701 SetUseCounter(
5702 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5703 ? eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly
5704 : eUseCounter_custom_DocumentQueryCommandStateOrValueReadOnly);
5705 break;
5706 case Command::SetDocumentInsertBROnEnterKeyPress:
5707 SetUseCounter(
5708 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5709 break;
5710 default:
5711 break;
5714 RefPtr<nsPresContext> presContext = GetPresContext();
5715 AutoEditorCommandTarget editCommandTarget(presContext, commandData);
5716 if (commandData.IsAvailableOnlyWhenEditable() &&
5717 !editCommandTarget.IsEditable(this)) {
5718 return;
5720 RefPtr<nsCommandParams> params = new nsCommandParams();
5721 // FYI: Only GetHTML command is not implemented by editor. Use window's
5722 // command table instead.
5723 if (editCommandTarget.IsEditor()) {
5724 MOZ_ASSERT(commandData.mCommand != Command::GetHTML);
5725 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5726 return;
5729 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5730 return;
5732 } else {
5733 // get command manager and dispatch command to our window if it's acceptable
5734 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5735 if (!commandManager) {
5736 return;
5739 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5740 if (!window) {
5741 return;
5744 // this is a special command since we are calling DoCommand rather than
5745 // GetCommandState like the other commands
5746 if (commandData.mCommand == Command::GetHTML) {
5747 if (NS_FAILED(params->SetBool("selection_only", true))) {
5748 return;
5750 if (NS_FAILED(params->SetCString("format", "text/html"_ns))) {
5751 return;
5753 if (NS_FAILED(commandManager->DoCommand(commandData.mXULCommandName,
5754 params, window))) {
5755 return;
5757 params->GetString("result", aValue);
5758 return;
5761 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5762 return;
5765 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5766 window, params))) {
5767 return;
5771 // If command does not have a state_attribute value, this call fails, and
5772 // aValue will wind up being the empty string. This is fine -- we want to
5773 // return "" in that case anyway (bug 738385), so we just return NS_OK
5774 // regardless.
5775 nsAutoCString result;
5776 params->GetCString("state_attribute", result);
5777 CopyUTF8toUTF16(result, aValue);
5780 void Document::MaybeEditingStateChanged() {
5781 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5782 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5783 if (nsContentUtils::IsSafeToRunScript()) {
5784 EditingStateChanged();
5785 } else if (!mInDestructor) {
5786 nsContentUtils::AddScriptRunner(
5787 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5788 &Document::MaybeEditingStateChanged));
5793 void Document::NotifyFetchOrXHRSuccess() {
5794 if (mShouldNotifyFetchSuccess) {
5795 nsContentUtils::DispatchEventOnlyToChrome(
5796 this, ToSupports(this), u"DOMDocFetchSuccess"_ns, CanBubble::eNo,
5797 Cancelable::eNo, /* DefaultAction */ nullptr);
5801 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
5802 mShouldNotifyFetchSuccess = aShouldNotify;
5805 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
5806 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
5809 void Document::TearingDownEditor() {
5810 if (IsEditingOn()) {
5811 mEditingState = EditingState::eTearingDown;
5812 if (IsHTMLOrXHTML()) {
5813 RemoveContentEditableStyleSheets();
5818 nsresult Document::TurnEditingOff() {
5819 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5821 nsPIDOMWindowOuter* window = GetWindow();
5822 if (!window) {
5823 return NS_ERROR_FAILURE;
5826 nsIDocShell* docshell = window->GetDocShell();
5827 if (!docshell) {
5828 return NS_ERROR_FAILURE;
5831 bool isBeingDestroyed = false;
5832 docshell->IsBeingDestroyed(&isBeingDestroyed);
5833 if (isBeingDestroyed) {
5834 return NS_ERROR_FAILURE;
5837 nsCOMPtr<nsIEditingSession> editSession;
5838 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5839 NS_ENSURE_SUCCESS(rv, rv);
5841 // turn editing off
5842 rv = editSession->TearDownEditorOnWindow(window);
5843 NS_ENSURE_SUCCESS(rv, rv);
5845 mEditingState = EditingState::eOff;
5847 // Editor resets selection since it is being destroyed. But if focus is
5848 // still into editable control, we have to initialize selection again.
5849 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5850 if (RefPtr<TextControlElement> textControlElement =
5851 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5852 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
5853 textEditor->ReinitializeSelection(*textControlElement);
5858 return NS_OK;
5861 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
5862 nsIDocShell* docShell = aWindow->GetDocShell();
5863 if (!docShell) {
5864 return false;
5866 return docShell->GetPresShell() != nullptr;
5869 nsresult Document::EditingStateChanged() {
5870 if (mRemovedFromDocShell) {
5871 return NS_OK;
5874 if (mEditingState == EditingState::eSettingUp ||
5875 mEditingState == EditingState::eTearingDown) {
5876 // XXX We shouldn't recurse
5877 return NS_OK;
5880 bool designMode = HasFlag(NODE_IS_EDITABLE);
5881 EditingState newState =
5882 designMode ? EditingState::eDesignMode
5883 : (mContentEditableCount > 0 ? EditingState::eContentEditable
5884 : EditingState::eOff);
5885 if (mEditingState == newState) {
5886 // No changes in editing mode.
5887 return NS_OK;
5890 if (newState == EditingState::eOff) {
5891 // Editing is being turned off.
5892 nsAutoScriptBlocker scriptBlocker;
5893 NotifyEditableStateChange(*this);
5894 return TurnEditingOff();
5897 // Flush out style changes on our _parent_ document, if any, so that
5898 // our check for a presshell won't get stale information.
5899 if (mParentDocument) {
5900 mParentDocument->FlushPendingNotifications(FlushType::Style);
5903 // get editing session, make sure this is a strong reference so the
5904 // window can't get deleted during the rest of this call.
5905 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5906 if (!window) {
5907 return NS_ERROR_FAILURE;
5910 nsIDocShell* docshell = window->GetDocShell();
5911 if (!docshell) {
5912 return NS_ERROR_FAILURE;
5915 // FlushPendingNotifications might destroy our docshell.
5916 bool isBeingDestroyed = false;
5917 docshell->IsBeingDestroyed(&isBeingDestroyed);
5918 if (isBeingDestroyed) {
5919 return NS_ERROR_FAILURE;
5922 nsCOMPtr<nsIEditingSession> editSession;
5923 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5924 NS_ENSURE_SUCCESS(rv, rv);
5926 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
5927 if (htmlEditor) {
5928 // We might already have an editor if it was set up for mail, let's see
5929 // if this is actually the case.
5930 uint32_t flags = 0;
5931 htmlEditor->GetFlags(&flags);
5932 if (flags & nsIEditor::eEditorMailMask) {
5933 // We already have a mail editor, then we should not attempt to create
5934 // another one.
5935 return NS_OK;
5939 if (!HasPresShell(window)) {
5940 // We should not make the window editable or setup its editor.
5941 // It's probably style=display:none.
5942 return NS_OK;
5945 bool makeWindowEditable = mEditingState == EditingState::eOff;
5946 bool spellRecheckAll = false;
5947 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
5948 htmlEditor = nullptr;
5951 EditingState oldState = mEditingState;
5952 nsAutoEditingState push(this, EditingState::eSettingUp);
5954 RefPtr<PresShell> presShell = GetPresShell();
5955 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
5957 // If we're entering the design mode from non-editable state, put the
5958 // selection at the beginning of the document for compatibility reasons.
5959 bool collapseSelectionAtBeginningOfDocument =
5960 designMode && oldState == EditingState::eOff;
5961 // However, mEditingState may be eOff even if there is some
5962 // `contenteditable` area and selection has been initialized for it because
5963 // mEditingState for `contenteditable` may have been scheduled to modify
5964 // when safe. In such case, we should not reinitialize selection.
5965 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
5966 Selection* selection =
5967 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
5968 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
5969 if (selection && selection->RangeCount()) {
5970 // Perhaps, we don't need to check whether the selection is in
5971 // an editing host or not because all contents will be editable
5972 // in designMode. (And we don't want to make this code so complicated
5973 // because of legacy API.)
5974 collapseSelectionAtBeginningOfDocument = false;
5978 MOZ_ASSERT(mStyleSetFilled);
5980 // Before making this window editable, we need to modify UA style sheet
5981 // because new style may change whether focused element will be focusable
5982 // or not.
5983 if (IsHTMLOrXHTML()) {
5984 AddContentEditableStyleSheetsToStyleSet(designMode);
5987 if (designMode) {
5988 // designMode is being turned on (overrides contentEditable).
5989 spellRecheckAll = oldState == EditingState::eContentEditable;
5992 // Adjust focused element with new style but blur event shouldn't be fired
5993 // until mEditingState is modified with newState.
5994 nsAutoScriptBlocker scriptBlocker;
5995 if (designMode) {
5996 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
5997 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
5998 window, nsFocusManager::eOnlyCurrentWindow,
5999 getter_AddRefs(focusedWindow));
6000 if (focusedContent) {
6001 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6002 bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
6003 : !focusedContent->IsFocusable();
6004 if (clearFocus) {
6005 nsFocusManager* fm = nsFocusManager::GetFocusManager();
6006 if (fm) {
6007 fm->ClearFocus(window);
6008 // If we need to dispatch blur event, we should put off after
6009 // modifying mEditingState since blur event handler may change
6010 // designMode state again.
6011 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6017 if (makeWindowEditable) {
6018 // Editing is being turned on (through designMode or contentEditable)
6019 // Turn on editor.
6020 // XXX This can cause flushing which can change the editing state, so make
6021 // sure to avoid recursing.
6022 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6023 NS_ENSURE_SUCCESS(rv, rv);
6026 // XXX Need to call TearDownEditorOnWindow for all failures.
6027 htmlEditor = docshell->GetHTMLEditor();
6028 if (!htmlEditor) {
6029 // Return NS_OK even though we've failed to create an editor here. This
6030 // is so that the setter of designMode on non-HTML documents does not
6031 // fail.
6032 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6033 // would detect that we can't support the mimetype if appropriate and
6034 // would fall onto the eEditorErrorCantEditMimeType path.
6035 return NS_OK;
6038 if (collapseSelectionAtBeginningOfDocument) {
6039 htmlEditor->BeginningOfDocument();
6042 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6043 nsContentUtils::AddScriptBlocker();
6047 mEditingState = newState;
6048 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6049 nsContentUtils::RemoveScriptBlocker();
6050 // If mEditingState is overwritten by another call and already disabled
6051 // the editing, we shouldn't keep making window editable.
6052 if (mEditingState == EditingState::eOff) {
6053 return NS_OK;
6057 if (makeWindowEditable) {
6058 // Set the editor to not insert br's on return when in p
6059 // elements by default.
6060 // XXX Do we only want to do this for designMode?
6061 // Note that it doesn't matter what CallerType we pass, because the callee
6062 // doesn't use it for this command. Play it safe and pass the more
6063 // restricted one.
6064 ErrorResult errorResult;
6065 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
6066 Unused << ExecCommand(u"insertBrOnReturn"_ns, false, u"false"_ns,
6067 // Principal doesn't matter here, because the
6068 // insertBrOnReturn command doesn't use it. Still
6069 // it's too bad we can't easily grab a nullprincipal
6070 // from somewhere without allocating one..
6071 *principal, errorResult);
6073 if (errorResult.Failed()) {
6074 // Editor setup failed. Editing is not on after all.
6075 // XXX Should we reset the editable flag on nodes?
6076 editSession->TearDownEditorOnWindow(window);
6077 mEditingState = EditingState::eOff;
6079 return errorResult.StealNSResult();
6083 // Resync the editor's spellcheck state.
6084 if (spellRecheckAll) {
6085 nsCOMPtr<nsISelectionController> selectionController =
6086 htmlEditor->GetSelectionController();
6087 if (NS_WARN_IF(!selectionController)) {
6088 return NS_ERROR_FAILURE;
6091 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6092 nsISelectionController::SELECTION_SPELLCHECK);
6093 if (spellCheckSelection) {
6094 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6097 htmlEditor->SyncRealTimeSpell();
6099 MaybeDispatchCheckKeyPressEventModelEvent();
6101 return NS_OK;
6104 // Helper class, used below in ChangeContentEditableCount().
6105 class DeferredContentEditableCountChangeEvent : public Runnable {
6106 public:
6107 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6108 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6109 mDoc(aDoc),
6110 mElement(aElement) {}
6112 NS_IMETHOD Run() override {
6113 if (mElement && mElement->OwnerDoc() == mDoc) {
6114 mDoc->DeferredContentEditableCountChange(mElement);
6116 return NS_OK;
6119 private:
6120 RefPtr<Document> mDoc;
6121 RefPtr<Element> mElement;
6124 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6125 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6126 "Trying to decrement too much.");
6128 mContentEditableCount += aChange;
6130 nsContentUtils::AddScriptRunner(
6131 new DeferredContentEditableCountChangeEvent(this, aElement));
6134 void Document::DeferredContentEditableCountChange(Element* aElement) {
6135 if (mParser ||
6136 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6137 return;
6140 EditingState oldState = mEditingState;
6142 nsresult rv = EditingStateChanged();
6143 NS_ENSURE_SUCCESS_VOID(rv);
6145 if (oldState == mEditingState &&
6146 mEditingState == EditingState::eContentEditable) {
6147 // We just changed the contentEditable state of a node, we need to reset
6148 // the spellchecking state of that node.
6149 if (aElement) {
6150 nsPIDOMWindowOuter* window = GetWindow();
6151 if (!window) {
6152 return;
6155 nsIDocShell* docshell = window->GetDocShell();
6156 if (!docshell) {
6157 return;
6160 RefPtr<HTMLEditor> htmlEditor = docshell->GetHTMLEditor();
6161 if (htmlEditor) {
6162 RefPtr<nsRange> range = nsRange::Create(aElement);
6163 IgnoredErrorResult res;
6164 range->SelectNode(*aElement, res);
6165 if (res.Failed()) {
6166 // The node might be detached from the document at this point,
6167 // which would cause this call to fail. In this case, we can
6168 // safely ignore the contenteditable count change.
6169 return;
6172 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6173 rv = htmlEditor->GetInlineSpellChecker(false,
6174 getter_AddRefs(spellChecker));
6175 NS_ENSURE_SUCCESS_VOID(rv);
6177 if (spellChecker) {
6178 rv = spellChecker->SpellCheckRange(range);
6179 NS_ENSURE_SUCCESS_VOID(rv);
6186 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6187 // Currently, we need to check only when we're becoming editable for
6188 // contenteditable.
6189 if (mEditingState != EditingState::eContentEditable) {
6190 return;
6193 if (mHasBeenEditable) {
6194 return;
6196 mHasBeenEditable = true;
6198 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6199 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6200 // with proper keypress event for the active web app.
6201 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6202 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6203 checkEvent.mFlags.mCancelable = false;
6204 checkEvent.mFlags.mBubbles = false;
6205 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6206 // Post the event rather than dispatching it synchronously because we need
6207 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6208 // can avoid paying unnecessary runtime cost for most web apps.
6209 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6212 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6213 PresShell* presShell = GetPresShell();
6214 if (!presShell) {
6215 return;
6217 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6220 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6222 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6223 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6224 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6225 aFocusTime >= mLastFocusTime);
6226 mLastFocusTime = aFocusTime;
6229 void Document::GetReferrer(nsAString& aReferrer) const {
6230 aReferrer.Truncate();
6231 if (!mReferrerInfo) {
6232 return;
6235 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6236 if (!referrer) {
6237 return;
6240 nsAutoCString uri;
6241 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
6242 if (NS_WARN_IF(NS_FAILED(rv))) {
6243 return;
6246 CopyUTF8toUTF16(uri, aReferrer);
6249 void Document::GetCookie(nsAString& aCookie, ErrorResult& rv) {
6250 aCookie.Truncate(); // clear current cookie in case service fails;
6251 // no cookie isn't an error condition.
6253 if (mDisableCookieAccess) {
6254 return;
6257 // If the document's sandboxed origin flag is set, access to read cookies
6258 // is prohibited.
6259 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6260 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
6261 return;
6264 StorageAccess storageAccess = StorageAllowedForDocument(this);
6265 if (storageAccess == StorageAccess::eDeny) {
6266 return;
6269 if (ShouldPartitionStorage(storageAccess) &&
6270 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6271 return;
6274 // If the document is a cookie-averse Document... return the empty string.
6275 if (IsCookieAverse()) {
6276 return;
6279 // not having a cookie service isn't an error
6280 nsCOMPtr<nsICookieService> service =
6281 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6282 if (service) {
6283 nsAutoCString cookie;
6284 service->GetCookieStringFromDocument(this, cookie);
6285 // CopyUTF8toUTF16 doesn't handle error
6286 // because it assumes that the input is valid.
6287 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6291 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6292 if (mDisableCookieAccess) {
6293 return;
6296 // If the document's sandboxed origin flag is set, access to write cookies
6297 // is prohibited.
6298 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6299 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
6300 return;
6303 StorageAccess storageAccess = StorageAllowedForDocument(this);
6304 if (storageAccess == StorageAccess::eDeny) {
6305 return;
6308 if (ShouldPartitionStorage(storageAccess) &&
6309 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6310 return;
6313 // If the document is a cookie-averse Document... do nothing.
6314 if (IsCookieAverse()) {
6315 return;
6318 if (!mDocumentURI) {
6319 return;
6322 // not having a cookie service isn't an error
6323 nsCOMPtr<nsICookieService> service =
6324 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6325 if (!service) {
6326 return;
6329 NS_ConvertUTF16toUTF8 cookie(aCookie);
6330 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6332 // No warning messages here.
6333 if (NS_FAILED(rv)) {
6334 return;
6337 nsCOMPtr<nsIObserverService> observerService =
6338 mozilla::services::GetObserverService();
6339 if (observerService) {
6340 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6341 nsString(aCookie).get());
6345 ReferrerPolicy Document::GetReferrerPolicy() const {
6346 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6347 : ReferrerPolicy::_empty;
6350 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6351 aAlinkColor.Truncate();
6353 HTMLBodyElement* body = GetBodyElement();
6354 if (body) {
6355 body->GetALink(aAlinkColor);
6359 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6360 HTMLBodyElement* body = GetBodyElement();
6361 if (body) {
6362 body->SetALink(aAlinkColor);
6366 void Document::GetLinkColor(nsAString& aLinkColor) {
6367 aLinkColor.Truncate();
6369 HTMLBodyElement* body = GetBodyElement();
6370 if (body) {
6371 body->GetLink(aLinkColor);
6375 void Document::SetLinkColor(const nsAString& aLinkColor) {
6376 HTMLBodyElement* body = GetBodyElement();
6377 if (body) {
6378 body->SetLink(aLinkColor);
6382 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6383 aVlinkColor.Truncate();
6385 HTMLBodyElement* body = GetBodyElement();
6386 if (body) {
6387 body->GetVLink(aVlinkColor);
6391 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6392 HTMLBodyElement* body = GetBodyElement();
6393 if (body) {
6394 body->SetVLink(aVlinkColor);
6398 void Document::GetBgColor(nsAString& aBgColor) {
6399 aBgColor.Truncate();
6401 HTMLBodyElement* body = GetBodyElement();
6402 if (body) {
6403 body->GetBgColor(aBgColor);
6407 void Document::SetBgColor(const nsAString& aBgColor) {
6408 HTMLBodyElement* body = GetBodyElement();
6409 if (body) {
6410 body->SetBgColor(aBgColor);
6414 void Document::GetFgColor(nsAString& aFgColor) {
6415 aFgColor.Truncate();
6417 HTMLBodyElement* body = GetBodyElement();
6418 if (body) {
6419 body->GetText(aFgColor);
6423 void Document::SetFgColor(const nsAString& aFgColor) {
6424 HTMLBodyElement* body = GetBodyElement();
6425 if (body) {
6426 body->SetText(aFgColor);
6430 void Document::CaptureEvents() {
6431 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6434 void Document::ReleaseEvents() {
6435 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6438 HTMLAllCollection* Document::All() {
6439 if (!mAll) {
6440 mAll = new HTMLAllCollection(this);
6442 return mAll;
6445 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6446 if (mIsSrcdocDocument) {
6447 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6448 if (inStrmChan) {
6449 return inStrmChan->GetSrcdocData(aSrcdocData);
6452 aSrcdocData = VoidString();
6453 return NS_OK;
6456 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6457 nsPIDOMWindowOuter* win = GetWindow();
6458 if (!win) {
6459 return nullptr;
6461 return WindowProxyHolder(win->GetBrowsingContext());
6464 nsIContent* Document::GetUnretargetedFocusedContent() const {
6465 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6466 if (!window) {
6467 return nullptr;
6469 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6470 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6471 window, nsFocusManager::eOnlyCurrentWindow,
6472 getter_AddRefs(focusedWindow));
6473 if (!focusedContent) {
6474 return nullptr;
6476 // be safe and make sure the element is from this document
6477 if (focusedContent->OwnerDoc() != this) {
6478 return nullptr;
6481 if (focusedContent->ChromeOnlyAccess()) {
6482 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6484 return focusedContent;
6487 Element* Document::GetActiveElement() {
6488 // Get the focused element.
6489 Element* focusedElement = GetRetargetedFocusedElement();
6490 if (focusedElement) {
6491 return focusedElement;
6494 // No focused element anywhere in this document. Try to get the BODY.
6495 if (IsHTMLOrXHTML()) {
6496 Element* bodyElement = AsHTMLDocument()->GetBody();
6497 if (bodyElement) {
6498 return bodyElement;
6500 // Special case to handle the transition to XHTML from XUL documents
6501 // where there currently isn't a body element, but we need to match the
6502 // XUL behavior. This should be removed when bug 1540278 is resolved.
6503 if (nsContentUtils::IsChromeDoc(this)) {
6504 Element* docElement = GetDocumentElement();
6505 if (docElement && docElement->IsXULElement()) {
6506 return docElement;
6509 // Because of IE compatibility, return null when html document doesn't have
6510 // a body.
6511 return nullptr;
6514 // If we couldn't get a BODY, return the root element.
6515 return GetDocumentElement();
6518 Element* Document::GetCurrentScript() {
6519 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6520 return el;
6523 void Document::ReleaseCapture() const {
6524 // only release the capture if the caller can access it. This prevents a
6525 // page from stopping a scrollbar grab for example.
6526 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6527 if (node && nsContentUtils::CanCallerAccess(node)) {
6528 PresShell::ReleaseCapturingContent();
6532 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6533 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6534 return mChromeXHRDocBaseURI;
6537 return GetDocBaseURI();
6540 void Document::SetBaseURI(nsIURI* aURI) {
6541 if (!aURI && !mDocumentBaseURI) {
6542 return;
6545 // Don't do anything if the URI wasn't actually changed.
6546 if (aURI && mDocumentBaseURI) {
6547 bool equalBases = false;
6548 mDocumentBaseURI->Equals(aURI, &equalBases);
6549 if (equalBases) {
6550 return;
6554 mDocumentBaseURI = aURI;
6555 RefreshLinkHrefs();
6558 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6559 const nsAString& aURI) {
6560 RefPtr<nsIURI> resolvedURI;
6561 MOZ_TRY(
6562 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6563 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6566 URLExtraData* Document::DefaultStyleAttrURLData() {
6567 MOZ_ASSERT(NS_IsMainThread());
6568 nsIURI* baseURI = GetDocBaseURI();
6569 nsIPrincipal* principal = NodePrincipal();
6570 bool equals;
6571 if (!mCachedURLData || mCachedURLData->BaseURI() != baseURI ||
6572 mCachedURLData->Principal() != principal || !mCachedReferrerInfo ||
6573 NS_FAILED(mCachedURLData->ReferrerInfo()->Equals(mCachedReferrerInfo,
6574 &equals)) ||
6575 !equals) {
6576 mCachedReferrerInfo = ReferrerInfo::CreateForInternalCSSResources(this);
6577 mCachedURLData = new URLExtraData(baseURI, mCachedReferrerInfo, principal);
6579 return mCachedURLData;
6582 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6583 if (mCharacterSet != aEncoding) {
6584 mCharacterSet = aEncoding;
6585 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6586 RecomputeLanguageFromCharset();
6588 if (nsPresContext* context = GetPresContext()) {
6589 context->DocumentCharSetChanged(aEncoding);
6594 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6595 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6598 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6599 aData.Truncate();
6600 const DocHeaderData* data = mHeaderData;
6601 while (data) {
6602 if (data->mField == aHeaderField) {
6603 aData = data->mData;
6605 break;
6607 data = data->mNext;
6611 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6612 if (!aHeaderField) {
6613 NS_ERROR("null headerField");
6614 return;
6617 if (!mHeaderData) {
6618 if (!aData.IsEmpty()) { // don't bother storing empty string
6619 mHeaderData = new DocHeaderData(aHeaderField, aData);
6621 } else {
6622 DocHeaderData* data = mHeaderData;
6623 DocHeaderData** lastPtr = &mHeaderData;
6624 bool found = false;
6625 do { // look for existing and replace
6626 if (data->mField == aHeaderField) {
6627 if (!aData.IsEmpty()) {
6628 data->mData.Assign(aData);
6629 } else { // don't store empty string
6630 *lastPtr = data->mNext;
6631 data->mNext = nullptr;
6632 delete data;
6634 found = true;
6636 break;
6638 lastPtr = &(data->mNext);
6639 data = *lastPtr;
6640 } while (data);
6642 if (!aData.IsEmpty() && !found) {
6643 // didn't find, append
6644 *lastPtr = new DocHeaderData(aHeaderField, aData);
6648 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6649 CopyUTF16toUTF8(aData, mContentLanguage);
6650 mMayNeedFontPrefsUpdate = true;
6651 if (auto* presContext = GetPresContext()) {
6652 presContext->ContentLanguageChanged();
6656 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6657 SetPreferredStyleSheetSet(aData);
6660 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6661 // We get into this code before we have a script global yet, so get to our
6662 // container via mDocumentContainer.
6663 if (nsCOMPtr<nsIRefreshURI> refresher = mDocumentContainer.get()) {
6664 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6665 // (used to just use the current URI of our webnavigation, but that
6666 // should really be the same thing). Note that this code can run
6667 // before the current URI of the webnavigation has been updated, so we
6668 // can't assert equality here.
6669 refresher->SetupRefreshURIFromHeader(mDocumentURI, NodePrincipal(),
6670 InnerWindowID(),
6671 NS_ConvertUTF16toUTF8(aData));
6675 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6676 mAllowDNSPrefetch) {
6677 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6678 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6681 if (aHeaderField == nsGkAtoms::viewport ||
6682 aHeaderField == nsGkAtoms::handheldFriendly) {
6683 mViewportType = Unknown;
6687 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6688 NotNull<const Encoding*>& aEncoding,
6689 nsHtml5TreeOpExecutor* aExecutor) {
6690 if (aChannel) {
6691 nsAutoCString charsetVal;
6692 nsresult rv = aChannel->GetContentCharset(charsetVal);
6693 if (NS_SUCCEEDED(rv)) {
6694 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6695 if (preferred) {
6696 aEncoding = WrapNotNull(preferred);
6697 aCharsetSource = kCharsetFromChannel;
6698 return;
6699 } else if (aExecutor && !charsetVal.IsEmpty()) {
6700 aExecutor->ComplainAboutBogusProtocolCharset(this);
6706 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6707 #ifdef DEBUG
6708 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6709 const Element* element = Element::FromNode(node);
6710 if (!element) {
6711 continue;
6713 MOZ_ASSERT(!element->HasServoData());
6715 #endif
6718 already_AddRefed<PresShell> Document::CreatePresShell(
6719 nsPresContext* aContext, nsViewManager* aViewManager) {
6720 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6722 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6724 AssertNoStaleServoDataIn(*this);
6726 RefPtr<PresShell> presShell = new PresShell(this);
6727 // Note: we don't hold a ref to the shell (it holds a ref to us)
6728 mPresShell = presShell;
6730 if (!mStyleSetFilled) {
6731 FillStyleSet();
6734 presShell->Init(aContext, aViewManager);
6736 // Gaining a shell causes changes in how media queries are evaluated, so
6737 // invalidate that.
6738 aContext->MediaFeatureValuesChanged(
6739 {MediaFeatureChange::kAllChanges},
6740 MediaFeatureChangePropagation::JustThisDocument);
6742 // Make sure to never paint if we belong to an invisible DocShell.
6743 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6744 if (docShell && docShell->IsInvisible()) {
6745 presShell->SetNeverPainting(true);
6748 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6749 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6750 presShell.get(), docShell.get()));
6752 mExternalResourceMap.ShowViewers();
6754 UpdateFrameRequestCallbackSchedulingState();
6756 if (mDocumentL10n) {
6757 // In case we already accumulated mutations,
6758 // we'll trigger the refresh driver now.
6759 mDocumentL10n->OnCreatePresShell();
6762 // Now that we have a shell, we might have @font-face rules (the presence of a
6763 // shell may change which rules apply to us). We don't need to do anything
6764 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
6765 // is ready to update we'll flush the font set.
6766 MarkUserFontSetDirty();
6768 // Take the author style disabled state from the top browsing cvontext.
6769 // (PageStyleChild.jsm ensures this is up to date.)
6770 if (BrowsingContext* bc = GetBrowsingContext()) {
6771 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
6774 return presShell.forget();
6777 void Document::UpdateFrameRequestCallbackSchedulingState(
6778 PresShell* aOldPresShell) {
6779 // If this condition changes to depend on some other variable, make sure to
6780 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
6781 // that variable can change. Also consider if you should change
6782 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
6783 // condition.
6784 bool shouldBeScheduled =
6785 WouldScheduleFrameRequestCallbacks() && !mFrameRequestCallbacks.IsEmpty();
6786 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
6787 // nothing to do
6788 return;
6791 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
6792 MOZ_RELEASE_ASSERT(presShell);
6794 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
6795 if (shouldBeScheduled) {
6796 rd->ScheduleFrameRequestCallbacks(this);
6797 } else {
6798 rd->RevokeFrameRequestCallbacks(this);
6801 mFrameRequestCallbacksScheduled = shouldBeScheduled;
6804 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
6805 MOZ_ASSERT(aCallbacks.IsEmpty());
6806 aCallbacks = std::move(mFrameRequestCallbacks);
6807 mCanceledFrameRequestCallbacks.clear();
6808 // No need to manually remove ourselves from the refresh driver; it will
6809 // handle that part. But we do have to update our state.
6810 mFrameRequestCallbacksScheduled = false;
6813 bool Document::ShouldThrottleFrameRequests() const {
6814 if (mStaticCloneCount > 0) {
6815 // Even if we're not visible, a static clone may be, so run at full speed.
6816 return false;
6819 if (Hidden()) {
6820 // We're not visible (probably in a background tab or the bf cache).
6821 return true;
6824 if (!mPresShell) {
6825 return false; // Can't do anything smarter.
6828 if (!mPresShell->IsActive()) {
6829 // The pres shell is not active (we're an invisible OOP iframe or such), so
6830 // throttle.
6831 return true;
6834 nsIFrame* frame = mPresShell->GetRootFrame();
6835 if (!frame) {
6836 return false; // Can't do anything smarter.
6839 nsIFrame* displayRootFrame = nsLayoutUtils::GetDisplayRootFrame(frame);
6840 if (!displayRootFrame) {
6841 return false; // Can't do anything smarter.
6844 if (!displayRootFrame->DidPaintPresShell(mPresShell)) {
6845 // We didn't get painted during the last paint, so we're not visible.
6846 // Throttle. Note that because we have to paint this document at least
6847 // once to unthrottle it, we will drop one requestAnimationFrame frame
6848 // when a document that previously wasn't visible scrolls into view. This
6849 // is acceptable since it would happen outside the viewport on APZ
6850 // platforms and is unlikely to be human-perceivable on non-APZ platforms.
6851 return true;
6854 // We got painted during the last paint, so run at full speed.
6855 return false;
6858 void Document::DeletePresShell() {
6859 mExternalResourceMap.HideViewers();
6860 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
6861 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
6864 // When our shell goes away, request that all our images be immediately
6865 // discarded, so we don't carry around decoded image data for a document we
6866 // no longer intend to paint.
6867 ImageTracker()->RequestDiscardAll();
6869 // Now that we no longer have a shell, we need to forget about any FontFace
6870 // objects for @font-face rules that came from the style set. There's no need
6871 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
6872 // no point on it.
6873 MarkUserFontSetDirty();
6875 if (mResizeObserverController) {
6876 mResizeObserverController->ShellDetachedFromDocument();
6879 if (IsEditingOn()) {
6880 TurnEditingOff();
6883 PresShell* oldPresShell = mPresShell;
6884 mPresShell = nullptr;
6885 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
6887 ClearStaleServoData();
6888 AssertNoStaleServoDataIn(*this);
6890 mStyleSet->ShellDetachedFromDocument();
6891 mStyleSetFilled = false;
6892 mQuirkSheetAdded = false;
6893 mContentEditableSheetAdded = false;
6894 mDesignModeSheetAdded = false;
6897 void Document::DisallowBFCaching() {
6898 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
6899 if (!mBFCacheDisallowed) {
6900 WindowGlobalChild* wgc = GetWindowGlobalChild();
6901 if (wgc) {
6902 wgc->BlockBFCacheFor(BFCacheStatus::NOT_ALLOWED);
6905 mBFCacheDisallowed = true;
6908 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
6909 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
6911 if (mPresShell) {
6912 if (aEntry) {
6913 mPresShell->StopObservingRefreshDriver();
6914 } else if (mBFCacheEntry) {
6915 mPresShell->StartObservingRefreshDriver();
6918 mBFCacheEntry = aEntry;
6921 bool Document::RemoveFromBFCacheSync() {
6922 bool removed = false;
6923 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
6924 entry->RemoveFromBFCacheSync();
6925 removed = true;
6926 } else if (!IsCurrentActiveDocument()) {
6927 // In the old bfcache implementation while the new page is loading, but
6928 // before nsIContentViewer.show() has been called, the previous page doesn't
6929 // yet have nsIBFCacheEntry. However, the previous page isn't the current
6930 // active document anymore.
6931 DisallowBFCaching();
6932 removed = true;
6935 if (XRE_IsContentProcess()) {
6936 if (BrowsingContext* bc = GetBrowsingContext()) {
6937 if (bc->IsInBFCache()) {
6938 ContentChild* cc = ContentChild::GetSingleton();
6939 // IPC is asynchronous but the caller is supposed to check the return
6940 // value. The reason for 'Sync' in the method name is that the old
6941 // implementation may run scripts. There is Async variant in
6942 // the old session history implementation for the cases where
6943 // synchronous operation isn't safe.
6944 cc->SendRemoveFromBFCache(bc->Top());
6945 removed = true;
6949 return removed;
6952 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
6953 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
6955 NS_RELEASE(e->mKey);
6956 if (e->mSubDocument) {
6957 e->mSubDocument->SetParentDocument(nullptr);
6958 NS_RELEASE(e->mSubDocument);
6962 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
6963 SubDocMapEntry* e =
6964 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
6966 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
6967 NS_ADDREF(e->mKey);
6969 e->mSubDocument = nullptr;
6972 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
6973 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
6975 if (!aSubDoc) {
6976 // aSubDoc is nullptr, remove the mapping
6978 if (mSubDocuments) {
6979 mSubDocuments->Remove(aElement);
6981 } else {
6982 if (!mSubDocuments) {
6983 // Create a new hashtable
6985 static const PLDHashTableOps hash_table_ops = {
6986 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
6987 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
6989 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
6992 // Add a mapping to the hash table
6993 auto entry =
6994 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
6996 if (!entry) {
6997 return NS_ERROR_OUT_OF_MEMORY;
7000 if (entry->mSubDocument) {
7001 entry->mSubDocument->SetParentDocument(nullptr);
7003 // Release the old sub document
7004 NS_RELEASE(entry->mSubDocument);
7007 entry->mSubDocument = aSubDoc;
7008 NS_ADDREF(entry->mSubDocument);
7010 aSubDoc->SetParentDocument(this);
7013 return NS_OK;
7016 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7017 if (mSubDocuments && aContent->IsElement()) {
7018 auto entry = static_cast<SubDocMapEntry*>(
7019 mSubDocuments->Search(aContent->AsElement()));
7021 if (entry) {
7022 return entry->mSubDocument;
7026 return nullptr;
7029 Element* Document::GetEmbedderElement() const {
7030 // We check if we're the active document in our BrowsingContext
7031 // by comparing against its document, rather than checking if the
7032 // WindowContext is cached, since mWindow may be null when we're
7033 // called (such as in nsPresContext::Init).
7034 if (BrowsingContext* bc = GetBrowsingContext()) {
7035 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7038 return nullptr;
7041 bool Document::IsNodeOfType(uint32_t aFlags) const { return false; }
7043 Element* Document::GetRootElement() const {
7044 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7045 ? mCachedRootElement
7046 : GetRootElementInternal();
7049 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7051 Element* Document::GetRootElementInternal() const {
7052 // We invoke GetRootElement() immediately before the servo traversal, so we
7053 // should always have a cache hit from Servo.
7054 MOZ_ASSERT(NS_IsMainThread());
7056 // Loop backwards because any non-elements, such as doctypes and PIs
7057 // are likely to appear before the root element.
7058 for (nsIContent* child = GetLastChild(); child;
7059 child = child->GetPreviousSibling()) {
7060 if (Element* element = Element::FromNode(child)) {
7061 const_cast<Document*>(this)->mCachedRootElement = element;
7062 return element;
7066 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7067 return nullptr;
7070 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7071 bool aNotify, ErrorResult& aRv) {
7072 if (aKid->IsElement() && GetRootElement()) {
7073 NS_WARNING("Inserting root element when we already have one");
7074 aRv.ThrowHierarchyRequestError("There is already a root element.");
7075 return;
7078 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7081 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7082 Maybe<mozAutoDocUpdate> updateBatch;
7083 if (aKid->IsElement()) {
7084 updateBatch.emplace(this, aNotify);
7085 // Destroy the link map up front before we mess with the child list.
7086 DestroyElementMaps();
7089 // Preemptively clear mCachedRootElement, since we may be about to remove it
7090 // from our child list, and we don't want to return this maybe-obsolete value
7091 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7092 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7093 // GetRootElement() calls until after it's removed the child from mChildren.
7094 // Any call before that point would restore this soon-to-be-obsolete cached
7095 // answer, and our clearing here would be fruitless.)
7096 mCachedRootElement = nullptr;
7097 nsINode::RemoveChildNode(aKid, aNotify);
7098 MOZ_ASSERT(mCachedRootElement != aKid,
7099 "Stale pointer in mCachedRootElement, after we tried to clear it "
7100 "(maybe somebody called GetRootElement() too early?)");
7103 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7104 if (mStyleSetFilled) {
7105 mStyleSet->AddDocStyleSheet(aSheet);
7106 ApplicableStylesChanged();
7110 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7111 mStyleSet->RecordShadowStyleChange(aShadowRoot);
7112 ApplicableStylesChanged();
7115 void Document::ApplicableStylesChanged() {
7116 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7117 // we need to always track style changes and remove the mStyleSetFilled.
7118 if (!mStyleSetFilled) {
7119 return;
7122 MarkUserFontSetDirty();
7123 PresShell* ps = GetPresShell();
7124 if (!ps) {
7125 return;
7128 ps->EnsureStyleFlush();
7129 nsPresContext* pc = ps->GetPresContext();
7130 if (!pc) {
7131 return;
7134 pc->MarkCounterStylesDirty();
7135 pc->MarkFontFeatureValuesDirty();
7136 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7139 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7140 if (mStyleSetFilled) {
7141 mStyleSet->RemoveStyleSheet(aSheet);
7142 ApplicableStylesChanged();
7146 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7147 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7149 if (aSheet.IsApplicable()) {
7150 AddStyleSheetToStyleSets(aSheet);
7154 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7155 const bool applicable = aSheet.IsApplicable();
7156 // If we're actually in the document style sheet list
7157 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7158 if (applicable) {
7159 AddStyleSheetToStyleSets(aSheet);
7160 } else {
7161 RemoveStyleSheetFromStyleSets(aSheet);
7165 PostStyleSheetApplicableStateChangeEvent(aSheet);
7167 if (!mSSApplicableStateNotificationPending) {
7168 MOZ_RELEASE_ASSERT(NS_IsMainThread());
7169 nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(
7170 "Document::NotifyStyleSheetApplicableStateChanged", this,
7171 &Document::NotifyStyleSheetApplicableStateChanged);
7172 mSSApplicableStateNotificationPending =
7173 NS_SUCCEEDED(Dispatch(TaskCategory::Other, notification.forget()));
7177 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7178 if (!StyleSheetChangeEventsEnabled()) {
7179 return;
7182 StyleSheetApplicableStateChangeEventInit init;
7183 init.mBubbles = true;
7184 init.mCancelable = true;
7185 init.mStylesheet = &aSheet;
7186 init.mApplicable = aSheet.IsApplicable();
7188 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7189 StyleSheetApplicableStateChangeEvent::Constructor(
7190 this, u"StyleSheetApplicableStateChanged"_ns, init);
7191 event->SetTrusted(true);
7192 event->SetTarget(this);
7193 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7194 new AsyncEventDispatcher(this, event);
7195 asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
7196 asyncDispatcher->PostDOMEvent();
7199 void Document::NotifyStyleSheetApplicableStateChanged() {
7200 mSSApplicableStateNotificationPending = false;
7201 nsCOMPtr<nsIObserverService> observerService =
7202 mozilla::services::GetObserverService();
7203 if (observerService) {
7204 observerService->NotifyObservers(
7205 ToSupports(this), "style-sheet-applicable-state-changed", nullptr);
7209 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7210 nsIURI* aSheetURI) {
7211 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7212 bool bEqual;
7213 nsIURI* uri = aSheets[i]->GetSheetURI();
7215 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7216 return i;
7219 return -1;
7222 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7223 nsIURI* aSheetURI) {
7224 MOZ_ASSERT(aSheetURI, "null arg");
7226 // Checking if we have loaded this one already.
7227 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7228 return NS_ERROR_INVALID_ARG;
7230 // Loading the sheet sync.
7231 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7233 css::SheetParsingMode parsingMode;
7234 switch (aType) {
7235 case Document::eAgentSheet:
7236 parsingMode = css::eAgentSheetFeatures;
7237 break;
7239 case Document::eUserSheet:
7240 parsingMode = css::eUserSheetFeatures;
7241 break;
7243 case Document::eAuthorSheet:
7244 parsingMode = css::eAuthorSheetFeatures;
7245 break;
7247 default:
7248 MOZ_CRASH("impossible value for aType");
7251 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7252 css::Loader::UseSystemPrincipal::Yes);
7253 if (result.isErr()) {
7254 return result.unwrapErr();
7257 RefPtr<StyleSheet> sheet = result.unwrap();
7259 sheet->SetAssociatedDocumentOrShadowRoot(this);
7260 MOZ_ASSERT(sheet->IsApplicable());
7262 return AddAdditionalStyleSheet(aType, sheet);
7265 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7266 StyleSheet* aSheet) {
7267 if (mAdditionalSheets[aType].Contains(aSheet)) {
7268 return NS_ERROR_INVALID_ARG;
7271 if (!aSheet->IsApplicable()) {
7272 return NS_ERROR_INVALID_ARG;
7275 mAdditionalSheets[aType].AppendElement(aSheet);
7277 if (mStyleSetFilled) {
7278 mStyleSet->AppendStyleSheet(*aSheet);
7279 ApplicableStylesChanged();
7281 return NS_OK;
7284 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7285 nsIURI* aSheetURI) {
7286 MOZ_ASSERT(aSheetURI);
7288 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7290 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7291 if (i >= 0) {
7292 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7293 sheets.RemoveElementAt(i);
7295 if (!mIsGoingAway) {
7296 MOZ_ASSERT(sheetRef->IsApplicable());
7297 if (mStyleSetFilled) {
7298 mStyleSet->RemoveStyleSheet(*sheetRef);
7299 ApplicableStylesChanged();
7302 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7306 nsIGlobalObject* Document::GetScopeObject() const {
7307 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7308 return scope;
7311 bool Document::CrossOriginIsolated() const {
7312 if (auto* bc = GetBrowsingContext()) {
7313 return bc->CrossOriginIsolated();
7316 // For a data document without a browsing context we check the
7317 // cross-origin-isolated state from its creator's inner window.
7318 if (mLoadedAsData) {
7319 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
7320 return window && window->GetBrowsingContext() &&
7321 window->GetBrowsingContext()->CrossOriginIsolated();
7324 return false;
7327 DocGroup* Document::GetDocGroupOrCreate() {
7328 if (!mDocGroup) {
7329 nsAutoCString docGroupKey;
7330 nsresult rv = mozilla::dom::DocGroup::GetKey(
7331 NodePrincipal(), CrossOriginIsolated(), docGroupKey);
7332 if (NS_SUCCEEDED(rv) && mDocumentContainer) {
7333 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7334 if (group) {
7335 mDocGroup = group->AddDocument(docGroupKey, this);
7339 return mDocGroup;
7342 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7343 mScopeObject = do_GetWeakReference(aGlobal);
7344 if (aGlobal) {
7345 mHasHadScriptHandlingObject = true;
7347 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
7348 if (!window) {
7349 return;
7351 BrowsingContextGroup* browsingContextGroup =
7352 window->GetBrowsingContextGroup();
7354 // We should already have the principal, and now that we have been added
7355 // to a window, we should be able to join a DocGroup!
7356 nsAutoCString docGroupKey;
7357 nsresult rv = mozilla::dom::DocGroup::GetKey(
7358 NodePrincipal(), CrossOriginIsolated(), docGroupKey);
7359 if (mDocGroup) {
7360 if (NS_SUCCEEDED(rv)) {
7361 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7363 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7364 browsingContextGroup);
7365 } else {
7366 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7368 MOZ_ASSERT(mDocGroup);
7371 MOZ_ASSERT_IF(
7372 mNodeInfoManager->GetArenaAllocator(),
7373 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7377 bool Document::ContainsEMEContent() {
7378 nsPIDOMWindowInner* win = GetInnerWindow();
7379 // Note this case is different from checking just media elements in that
7380 // it covers when we've created MediaKeys but not associated them with a
7381 // media element.
7382 return win && win->HasActiveMediaKeysInstance();
7385 bool Document::ContainsMSEContent() {
7386 bool containsMSE = false;
7388 auto check = [&containsMSE](nsISupports* aSupports) {
7389 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7390 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7391 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7392 if (ms) {
7393 containsMSE = true;
7398 EnumerateActivityObservers(check);
7399 return containsMSE;
7402 static void NotifyActivityChanged(nsISupports* aSupports) {
7403 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7404 if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7405 mediaElem->NotifyOwnerDocumentActivityChanged();
7407 nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
7408 do_QueryInterface(aSupports));
7409 if (objectLoadingContent) {
7410 nsObjectLoadingContent* olc =
7411 static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
7412 olc->NotifyOwnerDocumentActivityChanged();
7414 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7415 do_QueryInterface(aSupports));
7416 if (objectDocumentActivity) {
7417 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7418 } else {
7419 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7420 do_QueryInterface(aSupports));
7421 if (imageLoadingContent) {
7422 auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7423 ilc->NotifyOwnerDocumentActivityChanged();
7428 bool Document::IsTopLevelWindowInactive() const {
7429 if (BrowsingContext* bc = GetBrowsingContext()) {
7430 return !bc->GetIsActiveBrowserWindow();
7433 return false;
7436 void Document::SetContainer(nsDocShell* aContainer) {
7437 if (aContainer) {
7438 mDocumentContainer = aContainer;
7439 } else {
7440 mDocumentContainer = WeakPtr<nsDocShell>();
7443 mInChromeDocShell =
7444 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7446 EnumerateActivityObservers(NotifyActivityChanged);
7448 // IsTopLevelWindowInactive depends on the docshell, so
7449 // update the cached value now that it's available.
7450 UpdateDocumentStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE, false);
7451 if (!aContainer) {
7452 return;
7455 BrowsingContext* context = aContainer->GetBrowsingContext();
7456 if (context && context->IsContent()) {
7457 SetIsTopLevelContentDocument(context->IsTopContent());
7458 SetIsContentDocument(true);
7459 } else {
7460 SetIsTopLevelContentDocument(false);
7461 SetIsContentDocument(false);
7465 nsISupports* Document::GetContainer() const {
7466 return static_cast<nsIDocShell*>(mDocumentContainer);
7469 void Document::SetScriptGlobalObject(
7470 nsIScriptGlobalObject* aScriptGlobalObject) {
7471 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7472 mAnimationController->IsPausedByType(
7473 SMILTimeContainer::PAUSE_PAGEHIDE |
7474 SMILTimeContainer::PAUSE_BEGIN),
7475 "Clearing window pointer while animations are unpaused");
7477 if (mScriptGlobalObject && !aScriptGlobalObject) {
7478 // We're detaching from the window. We need to grab a pointer to
7479 // our layout history state now.
7480 mLayoutHistoryState = GetLayoutHistoryState();
7482 // Also make sure to remove our onload blocker now if we haven't done it yet
7483 if (mOnloadBlockCount != 0) {
7484 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7485 if (loadGroup) {
7486 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7490 if (GetController().isSome()) {
7491 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7492 loader->ClearCacheForControlledDocument(this);
7495 // We may become controlled again if this document comes back out
7496 // of bfcache. Clear our state to allow that to happen. Only
7497 // clear this flag if we are actually controlled, though, so pages
7498 // that were force reloaded don't become controlled when they
7499 // come out of bfcache.
7500 mMaybeServiceWorkerControlled = false;
7503 if (GetWindowContext()) {
7504 // The document is about to lose its window, so this is a good time to
7505 // send our page use counters, while we still have access to our
7506 // WindowContext.
7508 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7509 // catches some cases of documents losing their window that don't
7510 // get in here.)
7511 SendPageUseCounters();
7515 // BlockOnload() might be called before mScriptGlobalObject is set.
7516 // We may need to add the blocker once mScriptGlobalObject is set.
7517 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7519 mScriptGlobalObject = aScriptGlobalObject;
7521 if (needOnloadBlocker) {
7522 EnsureOnloadBlocker();
7525 UpdateFrameRequestCallbackSchedulingState();
7527 if (aScriptGlobalObject) {
7528 // Go back to using the docshell for the layout history state
7529 mLayoutHistoryState = nullptr;
7530 SetScopeObject(aScriptGlobalObject);
7531 mHasHadDefaultView = true;
7533 if (mAllowDNSPrefetch) {
7534 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7535 if (docShell) {
7536 #ifdef DEBUG
7537 nsCOMPtr<nsIWebNavigation> webNav =
7538 do_GetInterface(aScriptGlobalObject);
7539 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7540 "Unexpected container or script global?");
7541 #endif
7542 bool allowDNSPrefetch;
7543 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7544 mAllowDNSPrefetch = allowDNSPrefetch;
7548 // If we are set in a window that is already focused we should remember this
7549 // as the time the document gained focus.
7550 if (HasFocus(IgnoreErrors())) {
7551 SetLastFocusTime(TimeStamp::Now());
7555 // Remember the pointer to our window (or lack there of), to avoid
7556 // having to QI every time it's asked for.
7557 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7558 mWindow = window;
7560 // Now that we know what our window is, we can flush the CSP errors to the
7561 // Web Console. We are flushing all messages that occurred and were stored in
7562 // the queue prior to this point.
7563 if (mCSP) {
7564 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7567 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7568 do_QueryInterface(GetChannel());
7569 if (internalChannel) {
7570 nsCOMArray<nsISecurityConsoleMessage> messages;
7571 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7572 MOZ_ASSERT(NS_SUCCEEDED(rv));
7573 SendToConsole(messages);
7576 // Set our visibility state, but do not fire the event. This is correct
7577 // because either we're coming out of bfcache (in which case IsVisible() will
7578 // still test false at this point and no state change will happen) or we're
7579 // doing the initial document load and don't want to fire the event for this
7580 // change.
7582 // When the visibility is changed, notify it to observers.
7583 // Some observers need the notification, for example HTMLMediaElement uses
7584 // it to update internal media resource allocation.
7585 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7586 // creation are already done before Document::SetScriptGlobalObject() call.
7587 // MediaDecoder decides whether starting decoding is decided based on
7588 // document's visibility. When the MediaDecoder is created,
7589 // Document::SetScriptGlobalObject() is not yet called and document is
7590 // hidden state. Therefore the MediaDecoder decides that decoding is
7591 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7592 // call, the document becomes not hidden. At the time, MediaDecoder needs
7593 // to know it and needs to start updating decoding.
7594 UpdateVisibilityState(DispatchVisibilityChange::No);
7596 // The global in the template contents owner document should be the same.
7597 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7598 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7601 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7602 mScriptGlobalObject && GetChannel()) {
7603 // If we are shift-reloaded, don't associate with a ServiceWorker.
7604 if (mDocumentContainer->IsForceReloading()) {
7605 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7606 return;
7609 mMaybeServiceWorkerControlled = true;
7613 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7614 MOZ_ASSERT(!mScriptGlobalObject,
7615 "Do not call this when mScriptGlobalObject is set!");
7616 if (mHasHadDefaultView) {
7617 return nullptr;
7620 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7621 do_QueryReferent(mScopeObject);
7622 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7623 if (win) {
7624 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7625 if (!outer || outer->GetCurrentInnerWindow() != win) {
7626 NS_WARNING("Wrong inner/outer window combination!");
7627 return nullptr;
7630 return scriptHandlingObject;
7632 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7633 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7634 "Wrong script object!");
7635 if (aScriptObject) {
7636 SetScopeObject(aScriptObject);
7637 mHasHadDefaultView = false;
7641 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7642 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7643 // Let's use mScriptGlobalObject. Even if the document is already removed from
7644 // the docshell, the outer window might be still obtainable from the it.
7645 nsCOMPtr<nsPIDOMWindowOuter> win;
7646 if (mRemovedFromDocShell) {
7647 // The docshell returns the outer window we are done.
7648 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7649 if (kungFuDeathGrip) {
7650 win = kungFuDeathGrip->GetWindow();
7652 } else {
7653 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7654 do_QueryInterface(mScriptGlobalObject)) {
7655 // mScriptGlobalObject is always the inner window, let's get the outer.
7656 win = inner->GetOuterWindow();
7660 return win;
7663 bool Document::InternalAllowXULXBL() {
7664 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7665 mAllowXULXBL = eTriTrue;
7666 return true;
7669 mAllowXULXBL = eTriFalse;
7670 return false;
7673 // Note: We don't hold a reference to the document observer; we assume
7674 // that it has a live reference to the document.
7675 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7676 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7677 "Observer already in the list");
7678 mObservers.AppendElement(aObserver);
7679 AddMutationObserver(aObserver);
7682 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7683 // If we're in the process of destroying the document (and we're
7684 // informing the observers of the destruction), don't remove the
7685 // observers from the list. This is not a big deal, since we
7686 // don't hold a live reference to the observers.
7687 if (!mInDestructor) {
7688 RemoveMutationObserver(aObserver);
7689 return mObservers.RemoveElement(aObserver);
7692 return mObservers.Contains(aObserver);
7695 void Document::BeginUpdate() {
7696 ++mUpdateNestLevel;
7697 nsContentUtils::AddScriptBlocker();
7698 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7701 void Document::EndUpdate() {
7702 const bool reset = !mPendingMaybeEditingStateChanged;
7703 mPendingMaybeEditingStateChanged = true;
7705 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7707 --mUpdateNestLevel;
7709 nsContentUtils::RemoveScriptBlocker();
7711 if (mXULBroadcastManager) {
7712 mXULBroadcastManager->MaybeBroadcast();
7715 if (reset) {
7716 mPendingMaybeEditingStateChanged = false;
7718 MaybeEditingStateChanged();
7721 void Document::BeginLoad() {
7722 if (IsEditingOn()) {
7723 // Reset() blows away all event listeners in the document, and our
7724 // editor relies heavily on those. Midas is turned on, to make it
7725 // work, re-initialize it to give it a chance to add its event
7726 // listeners again.
7728 TurnEditingOff();
7729 EditingStateChanged();
7732 MOZ_ASSERT(!mDidCallBeginLoad);
7733 mDidCallBeginLoad = true;
7735 // Block onload here to prevent having to deal with blocking and
7736 // unblocking it while we know the document is loading.
7737 BlockOnload();
7738 mDidFireDOMContentLoaded = false;
7739 BlockDOMContentLoaded();
7741 if (mScriptLoader) {
7742 mScriptLoader->BeginDeferringScripts();
7745 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
7748 void Document::MozSetImageElement(const nsAString& aImageElementId,
7749 Element* aElement) {
7750 if (aImageElementId.IsEmpty()) return;
7752 // Hold a script blocker while calling SetImageElement since that can call
7753 // out to id-observers
7754 nsAutoScriptBlocker scriptBlocker;
7756 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
7757 if (entry) {
7758 entry->SetImageElement(aElement);
7759 if (entry->IsEmpty()) {
7760 mIdentifierMap.RemoveEntry(entry);
7765 void Document::DispatchContentLoadedEvents() {
7766 // If you add early returns from this method, make sure you're
7767 // calling UnblockOnload properly.
7769 // Unpin references to preloaded images
7770 mPreloadingImages.Clear();
7772 // DOM manipulation after content loaded should not care if the element
7773 // came from the preloader.
7774 mPreloadedPreconnects.Clear();
7776 if (mTiming) {
7777 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
7780 // Dispatch observer notification to notify observers document is interactive.
7781 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
7782 if (os) {
7783 nsIPrincipal* principal = NodePrincipal();
7784 os->NotifyObservers(ToSupports(this),
7785 principal->IsSystemPrincipal()
7786 ? "chrome-document-interactive"
7787 : "content-document-interactive",
7788 nullptr);
7791 // Fire a DOM event notifying listeners that this document has been
7792 // loaded (excluding images and other loads initiated by this
7793 // document).
7794 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
7795 u"DOMContentLoaded"_ns, CanBubble::eYes,
7796 Cancelable::eNo);
7798 if (auto* const window = GetInnerWindow()) {
7799 const RefPtr<ServiceWorkerContainer> serviceWorker =
7800 window->Navigator()->ServiceWorker();
7802 // This could cause queued messages from a service worker to get
7803 // dispatched on serviceWorker.
7804 serviceWorker->StartMessages();
7807 if (MayStartLayout()) {
7808 MaybeResolveReadyForIdle();
7811 RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
7812 nsIDocShell* docShell = this->GetDocShell();
7814 if (timelines && timelines->HasConsumer(docShell)) {
7815 timelines->AddMarkerForDocShell(
7816 docShell,
7817 MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
7820 if (mTiming) {
7821 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
7824 // If this document is a [i]frame, fire a DOMFrameContentLoaded
7825 // event on all parent documents notifying that the HTML (excluding
7826 // other external files such as images and stylesheets) in a frame
7827 // has finished loading.
7829 // target_frame is the [i]frame element that will be used as the
7830 // target for the event. It's the [i]frame whose content is done
7831 // loading.
7832 nsCOMPtr<Element> target_frame = GetEmbedderElement();
7834 if (target_frame && target_frame->IsInComposedDoc()) {
7835 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
7836 while (parent) {
7837 RefPtr<Event> event;
7838 if (parent) {
7839 IgnoredErrorResult ignored;
7840 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
7843 if (event) {
7844 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
7846 event->SetTarget(target_frame);
7847 event->SetTrusted(true);
7849 // To dispatch this event we must manually call
7850 // EventDispatcher::Dispatch() on the ancestor document since the
7851 // target is not in the same document, so the event would never reach
7852 // the ancestor document if we used the normal event
7853 // dispatching code.
7855 WidgetEvent* innerEvent = event->WidgetEventPtr();
7856 if (innerEvent) {
7857 nsEventStatus status = nsEventStatus_eIgnore;
7859 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
7860 EventDispatcher::Dispatch(ToSupports(parent), context, innerEvent,
7861 event, &status);
7866 parent = parent->GetInProcessParentDocument();
7870 // If the document has a manifest attribute, fire a MozApplicationManifest
7871 // event.
7872 Element* root = GetRootElement();
7873 if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::manifest)) {
7874 nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
7875 u"MozApplicationManifest"_ns,
7876 CanBubble::eYes, Cancelable::eYes);
7879 nsPIDOMWindowInner* inner = GetInnerWindow();
7880 if (inner) {
7881 inner->NoteDOMContentLoaded();
7884 // TODO
7885 if (mMaybeServiceWorkerControlled) {
7886 using mozilla::dom::ServiceWorkerManager;
7887 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
7888 if (swm) {
7889 Maybe<ClientInfo> clientInfo = GetClientInfo();
7890 if (clientInfo.isSome()) {
7891 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
7896 if (mSetCompleteAfterDOMContentLoaded) {
7897 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
7898 mSetCompleteAfterDOMContentLoaded = false;
7901 UnblockOnload(true);
7904 void Document::EndLoad() {
7905 bool turnOnEditing =
7906 mParser && (HasFlag(NODE_IS_EDITABLE) || mContentEditableCount > 0);
7908 #if defined(DEBUG)
7909 // only assert if nothing stopped the load on purpose
7910 if (!mParserAborted) {
7911 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
7913 #endif
7915 // EndLoad may have been called without a matching call to BeginLoad, in the
7916 // case of a failed parse (for example, due to timeout). In such a case, we
7917 // still want to execute part of this code to do appropriate cleanup, but we
7918 // gate part of it because it is intended to match 1-for-1 with calls to
7919 // BeginLoad. We have an explicit flag bit for this purpose, since it's
7920 // complicated and error prone to derive this condition from other related
7921 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
7923 // Part 1: Code that always executes to cleanup end of parsing, whether
7924 // that parsing was successful or not.
7926 // Drop the ref to our parser, if any, but keep hold of the sink so that we
7927 // can flush it from FlushPendingNotifications as needed. We might have to
7928 // do that to get a StartLayout() to happen.
7929 if (mParser) {
7930 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
7931 mParser = nullptr;
7934 // Update the attributes on the PerformanceNavigationTiming before notifying
7935 // the onload observers.
7936 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
7937 if (RefPtr<Performance> performance = window->GetPerformance()) {
7938 performance->UpdateNavigationTimingEntry();
7942 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
7944 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
7946 if (!mDidCallBeginLoad) {
7947 return;
7949 mDidCallBeginLoad = false;
7951 UnblockDOMContentLoaded();
7953 if (turnOnEditing) {
7954 EditingStateChanged();
7957 if (!GetWindow()) {
7958 // This is a document that's not in a window. For example, this could be an
7959 // XMLHttpRequest responseXML document, or a document created via DOMParser
7960 // or DOMImplementation. We don't reach this code normally for such
7961 // documents (which is not obviously correct), but can reach it via
7962 // document.open()/document.close().
7964 // Such documents don't fire load events, but per spec should set their
7965 // readyState to "complete" when parsing and all loading of subresources is
7966 // done. Parsing is done now, and documents not in a window don't load
7967 // subresources, so just go ahead and mark ourselves as complete.
7968 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
7969 /* updateTimingInformation = */ false);
7971 // Reset mSkipLoadEventAfterClose just in case.
7972 mSkipLoadEventAfterClose = false;
7976 void Document::UnblockDOMContentLoaded() {
7977 MOZ_ASSERT(mBlockDOMContentLoaded);
7978 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
7979 return;
7982 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
7983 ("DOCUMENT %p UnblockDOMContentLoaded", this));
7985 mDidFireDOMContentLoaded = true;
7986 if (PresShell* presShell = GetPresShell()) {
7987 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
7990 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
7991 if (!mSynchronousDOMContentLoaded) {
7992 MOZ_RELEASE_ASSERT(NS_IsMainThread());
7993 nsCOMPtr<nsIRunnable> ev =
7994 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
7995 &Document::DispatchContentLoadedEvents);
7996 Dispatch(TaskCategory::Other, ev.forget());
7997 } else {
7998 DispatchContentLoadedEvents();
8002 void Document::ContentStateChanged(nsIContent* aContent,
8003 EventStates aStateMask) {
8004 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8005 "Someone forgot a scriptblocker");
8006 NS_DOCUMENT_NOTIFY_OBSERVERS(ContentStateChanged,
8007 (this, aContent, aStateMask));
8010 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8011 StyleRuleChangeKind) {
8012 if (aSheet.IsApplicable()) {
8013 ApplicableStylesChanged();
8017 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8018 if (aRule.IsIncompleteImportRule()) {
8019 return;
8022 if (aSheet.IsApplicable()) {
8023 ApplicableStylesChanged();
8027 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8028 if (aSheet.IsApplicable()) {
8029 ApplicableStylesChanged();
8033 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8034 if (aSheet.IsApplicable()) {
8035 ApplicableStylesChanged();
8039 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8040 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8041 return nullptr;
8044 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8047 static void InsertAnonContentIntoCanvas(AnonymousContent& aAnonContent,
8048 PresShell* aPresShell) {
8049 Element* container = GetCustomContentContainer(aPresShell);
8050 if (!container) {
8051 return;
8054 IgnoredErrorResult rv;
8055 container->AppendChildTo(&aAnonContent.ContentNode(), true, rv);
8056 if (rv.Failed()) {
8057 return;
8060 aPresShell->GetCanvasFrame()->ShowCustomContentContainer();
8063 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8064 Element& aElement, ErrorResult& aRv) {
8065 nsAutoScriptBlocker scriptBlocker;
8067 // Clone the node to avoid returning a direct reference.
8068 nsCOMPtr<nsINode> clone = aElement.CloneNode(true, aRv);
8069 if (aRv.Failed()) {
8070 return nullptr;
8073 auto anonContent =
8074 MakeRefPtr<AnonymousContent>(clone.forget().downcast<Element>());
8075 mAnonymousContents.AppendElement(anonContent);
8077 InsertAnonContentIntoCanvas(*anonContent, GetPresShell());
8079 return anonContent.forget();
8082 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8083 PresShell* aPresShell) {
8084 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8085 if (!container) {
8086 return;
8088 container->RemoveChild(aAnonContent.ContentNode(), IgnoreErrors());
8091 void Document::RemoveAnonymousContent(AnonymousContent& aContent,
8092 ErrorResult& aRv) {
8093 nsAutoScriptBlocker scriptBlocker;
8095 auto index = mAnonymousContents.IndexOf(&aContent);
8096 if (index == mAnonymousContents.NoIndex) {
8097 return;
8100 mAnonymousContents.RemoveElementAt(index);
8101 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8103 if (mAnonymousContents.IsEmpty() &&
8104 GetCustomContentContainer(GetPresShell())) {
8105 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8109 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8110 nsINode* aNode) const {
8111 if (!aNode->IsInNativeAnonymousSubtree()) {
8112 return nullptr;
8115 PresShell* presShell = GetPresShell();
8116 if (!presShell || !presShell->GetCanvasFrame()) {
8117 return nullptr;
8120 nsAutoScriptBlocker scriptBlocker;
8121 nsCOMPtr<Element> customContainer =
8122 presShell->GetCanvasFrame()->GetCustomContentContainer();
8123 if (!customContainer) {
8124 return nullptr;
8127 // An arbitrary number of elements can be inserted as children of the custom
8128 // container frame. We want the one that was added that contains aNode, so
8129 // we need to keep track of the last child separately using |child| here.
8130 nsINode* child = aNode;
8131 nsINode* parent = aNode->GetParentNode();
8132 while (parent && parent->IsInNativeAnonymousSubtree()) {
8133 if (parent == customContainer) {
8134 return Element::FromNode(child);
8136 child = parent;
8137 parent = child->GetParentNode();
8139 return nullptr;
8142 Maybe<ClientInfo> Document::GetClientInfo() const {
8143 if (const Document* orig = GetOriginalDocument()) {
8144 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8145 return info;
8149 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8150 return inner->GetClientInfo();
8153 return Maybe<ClientInfo>();
8156 Maybe<ClientState> Document::GetClientState() const {
8157 if (const Document* orig = GetOriginalDocument()) {
8158 if (Maybe<ClientState> state = orig->GetClientState()) {
8159 return state;
8163 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8164 return inner->GetClientState();
8167 return Maybe<ClientState>();
8170 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8171 if (const Document* orig = GetOriginalDocument()) {
8172 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8173 return controller;
8177 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8178 return inner->GetController();
8181 return Maybe<ServiceWorkerDescriptor>();
8185 // Document interface
8187 DocumentType* Document::GetDoctype() const {
8188 for (nsIContent* child = GetFirstChild(); child;
8189 child = child->GetNextSibling()) {
8190 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8191 return static_cast<DocumentType*>(child);
8194 return nullptr;
8197 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8198 if (!mDOMImplementation) {
8199 nsCOMPtr<nsIURI> uri;
8200 NS_NewURI(getter_AddRefs(uri), "about:blank");
8201 if (!uri) {
8202 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8203 return nullptr;
8205 bool hasHadScriptObject = true;
8206 nsIScriptGlobalObject* scriptObject =
8207 GetScriptHandlingObject(hasHadScriptObject);
8208 if (!scriptObject && hasHadScriptObject) {
8209 rv.Throw(NS_ERROR_UNEXPECTED);
8210 return nullptr;
8212 mDOMImplementation = new DOMImplementation(
8213 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8216 return mDOMImplementation;
8219 bool IsLowercaseASCII(const nsAString& aValue) {
8220 int32_t len = aValue.Length();
8221 for (int32_t i = 0; i < len; ++i) {
8222 char16_t c = aValue[i];
8223 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8224 return false;
8227 return true;
8230 already_AddRefed<Element> Document::CreateElement(
8231 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8232 ErrorResult& rv) {
8233 rv = nsContentUtils::CheckQName(aTagName, false);
8234 if (rv.Failed()) {
8235 return nullptr;
8238 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8239 nsAutoString lcTagName;
8240 if (needsLowercase) {
8241 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8244 const nsString* is = nullptr;
8245 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8246 if (aOptions.IsElementCreationOptions()) {
8247 const ElementCreationOptions& options =
8248 aOptions.GetAsElementCreationOptions();
8250 if (options.mIs.WasPassed()) {
8251 is = &options.mIs.Value();
8254 // Check 'pseudo' and throw an exception if it's not one allowed
8255 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8256 if (options.mPseudo.WasPassed()) {
8257 Maybe<PseudoStyleType> type =
8258 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8259 if (!type || *type == PseudoStyleType::NotPseudo ||
8260 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8261 rv.ThrowNotSupportedError("Invalid pseudo-element");
8262 return nullptr;
8264 pseudoType = *type;
8268 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8269 nullptr, mDefaultElementType, is);
8271 if (pseudoType != PseudoStyleType::NotPseudo) {
8272 elem->SetPseudoElementType(pseudoType);
8275 return elem.forget();
8278 already_AddRefed<Element> Document::CreateElementNS(
8279 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8280 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8281 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8282 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8283 mNodeInfoManager, ELEMENT_NODE,
8284 getter_AddRefs(nodeInfo));
8285 if (rv.Failed()) {
8286 return nullptr;
8289 const nsString* is = nullptr;
8290 if (aOptions.IsElementCreationOptions()) {
8291 const ElementCreationOptions& options =
8292 aOptions.GetAsElementCreationOptions();
8293 if (options.mIs.WasPassed()) {
8294 is = &options.mIs.Value();
8298 nsCOMPtr<Element> element;
8299 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8300 NOT_FROM_PARSER, is);
8301 if (rv.Failed()) {
8302 return nullptr;
8305 return element.forget();
8308 already_AddRefed<Element> Document::CreateXULElement(
8309 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8310 ErrorResult& aRv) {
8311 aRv = nsContentUtils::CheckQName(aTagName, false);
8312 if (aRv.Failed()) {
8313 return nullptr;
8316 const nsString* is = nullptr;
8317 if (aOptions.IsElementCreationOptions()) {
8318 const ElementCreationOptions& options =
8319 aOptions.GetAsElementCreationOptions();
8320 if (options.mIs.WasPassed()) {
8321 is = &options.mIs.Value();
8325 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8326 if (!elem) {
8327 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8328 return nullptr;
8330 return elem.forget();
8333 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8334 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8335 return text.forget();
8338 already_AddRefed<nsTextNode> Document::CreateTextNode(
8339 const nsAString& aData) const {
8340 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8341 // Don't notify; this node is still being created.
8342 text->SetText(aData, false);
8343 return text.forget();
8346 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8347 RefPtr<DocumentFragment> frag =
8348 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8349 return frag.forget();
8352 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
8353 already_AddRefed<dom::Comment> Document::CreateComment(
8354 const nsAString& aData) const {
8355 RefPtr<dom::Comment> comment =
8356 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8358 // Don't notify; this node is still being created.
8359 comment->SetText(aData, false);
8360 return comment.forget();
8363 already_AddRefed<CDATASection> Document::CreateCDATASection(
8364 const nsAString& aData, ErrorResult& rv) {
8365 if (IsHTMLDocument()) {
8366 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8367 return nullptr;
8370 if (FindInReadable(u"]]>"_ns, aData)) {
8371 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8372 return nullptr;
8375 RefPtr<CDATASection> cdata =
8376 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8378 // Don't notify; this node is still being created.
8379 cdata->SetText(aData, false);
8381 return cdata.forget();
8384 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8385 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8386 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8387 if (NS_FAILED(res)) {
8388 rv.Throw(res);
8389 return nullptr;
8392 if (FindInReadable(u"?>"_ns, aData)) {
8393 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8394 return nullptr;
8397 RefPtr<ProcessingInstruction> pi =
8398 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8400 return pi.forget();
8403 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8404 ErrorResult& rv) {
8405 if (!mNodeInfoManager) {
8406 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8407 return nullptr;
8410 nsresult res = nsContentUtils::CheckQName(aName, false);
8411 if (NS_FAILED(res)) {
8412 rv.Throw(res);
8413 return nullptr;
8416 nsAutoString name;
8417 if (IsHTMLDocument()) {
8418 nsContentUtils::ASCIIToLower(aName, name);
8419 } else {
8420 name = aName;
8423 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8424 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8425 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8426 if (NS_FAILED(res)) {
8427 rv.Throw(res);
8428 return nullptr;
8431 RefPtr<Attr> attribute =
8432 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8433 return attribute.forget();
8436 already_AddRefed<Attr> Document::CreateAttributeNS(
8437 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8438 ErrorResult& rv) {
8439 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8440 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8441 mNodeInfoManager, ATTRIBUTE_NODE,
8442 getter_AddRefs(nodeInfo));
8443 if (rv.Failed()) {
8444 return nullptr;
8447 RefPtr<Attr> attribute =
8448 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8449 return attribute.forget();
8452 void Document::ResolveScheduledSVGPresAttrs() {
8453 for (SVGElement* svg : mLazySVGPresElements) {
8454 svg->UpdateContentDeclarationBlock();
8456 mLazySVGPresElements.Clear();
8459 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8460 const {
8461 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8463 const nsTArray<nsWeakPtr> blockedNodes = mBlockedNodesByClassifier.Clone();
8465 for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
8466 nsWeakPtr weakNode = blockedNodes[i];
8467 nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
8468 // Consider only nodes to which we have managed to get strong references.
8469 // Coping with nullptrs since it's expected for nodes to disappear when
8470 // nobody else is referring to them.
8471 if (node) {
8472 list->AppendElement(node);
8476 return list.forget();
8479 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8480 aSheetSet.Truncate();
8482 // Look through our sheets, find the selected set title
8483 size_t count = SheetCount();
8484 nsAutoString title;
8485 for (size_t index = 0; index < count; index++) {
8486 StyleSheet* sheet = SheetAt(index);
8487 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8489 if (sheet->Disabled()) {
8490 // Disabled sheets don't affect the currently selected set
8491 continue;
8494 sheet->GetTitle(title);
8496 if (aSheetSet.IsEmpty()) {
8497 aSheetSet = title;
8498 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8499 // Sheets from multiple sets enabled; return null string, per spec.
8500 SetDOMStringToNull(aSheetSet);
8501 return;
8506 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8507 if (DOMStringIsNull(aSheetSet)) {
8508 return;
8511 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8512 // or CSSLoaders.
8513 mLastStyleSheetSet = aSheetSet;
8514 EnableStyleSheetsForSetInternal(aSheetSet, true);
8517 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8518 mPreferredStyleSheetSet = aSheetSet;
8519 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8520 // spec.
8521 if (DOMStringIsNull(mLastStyleSheetSet)) {
8522 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8523 // per spec. The idea here is that we're changing our preferred set and
8524 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8525 // using the Internal version so we can update the CSSLoader and not have
8526 // to worry about null strings.
8527 EnableStyleSheetsForSetInternal(aSheetSet, true);
8531 DOMStringList* Document::StyleSheetSets() {
8532 if (!mStyleSheetSetList) {
8533 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8535 return mStyleSheetSetList;
8538 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8539 // Per spec, passing in null is a no-op.
8540 if (!DOMStringIsNull(aSheetSet)) {
8541 // Note: must make sure to not change the CSSLoader's preferred sheet --
8542 // that value should be equal to either our lastStyleSheetSet (if that's
8543 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8544 // change either of those.
8545 EnableStyleSheetsForSetInternal(aSheetSet, false);
8549 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8550 bool aUpdateCSSLoader) {
8551 size_t count = SheetCount();
8552 nsAutoString title;
8553 for (size_t index = 0; index < count; index++) {
8554 StyleSheet* sheet = SheetAt(index);
8555 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8557 sheet->GetTitle(title);
8558 if (!title.IsEmpty()) {
8559 sheet->SetEnabled(title.Equals(aSheetSet));
8562 if (aUpdateCSSLoader) {
8563 CSSLoader()->DocumentStyleSheetSetChanged();
8565 if (mStyleSet->StyleSheetsHaveChanged()) {
8566 ApplicableStylesChanged();
8570 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8571 nsAutoCString charset;
8572 GetDocumentCharacterSet()->Name(charset);
8573 CopyASCIItoUTF16(charset, aCharacterSet);
8576 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8577 ErrorResult& rv) const {
8578 nsINode* imported = &aNode;
8580 switch (imported->NodeType()) {
8581 case DOCUMENT_NODE: {
8582 break;
8584 case DOCUMENT_FRAGMENT_NODE:
8585 case ATTRIBUTE_NODE:
8586 case ELEMENT_NODE:
8587 case PROCESSING_INSTRUCTION_NODE:
8588 case TEXT_NODE:
8589 case CDATA_SECTION_NODE:
8590 case COMMENT_NODE:
8591 case DOCUMENT_TYPE_NODE: {
8592 return imported->Clone(aDeep, mNodeInfoManager, rv);
8594 default: {
8595 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8599 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8600 return nullptr;
8603 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8604 return nsRange::Create(this, 0, this, 0, rv);
8607 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8608 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8609 ErrorResult& rv) const {
8610 RefPtr<NodeIterator> iterator =
8611 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8612 return iterator.forget();
8615 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8616 uint32_t aWhatToShow,
8617 NodeFilter* aFilter,
8618 ErrorResult& rv) const {
8619 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8620 return walker.forget();
8623 already_AddRefed<Location> Document::GetLocation() const {
8624 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8626 if (!w) {
8627 return nullptr;
8630 return do_AddRef(w->Location());
8633 already_AddRefed<nsIURI> Document::GetDomainURI() {
8634 nsIPrincipal* principal = NodePrincipal();
8636 nsCOMPtr<nsIURI> uri;
8637 principal->GetDomain(getter_AddRefs(uri));
8638 if (uri) {
8639 return uri.forget();
8641 auto* basePrin = BasePrincipal::Cast(principal);
8642 basePrin->GetURI(getter_AddRefs(uri));
8643 return uri.forget();
8646 void Document::GetDomain(nsAString& aDomain) {
8647 nsCOMPtr<nsIURI> uri = GetDomainURI();
8649 if (!uri) {
8650 aDomain.Truncate();
8651 return;
8654 nsAutoCString hostName;
8655 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8656 if (NS_SUCCEEDED(rv)) {
8657 CopyUTF8toUTF16(hostName, aDomain);
8658 } else {
8659 // If we can't get the host from the URI (e.g. about:, javascript:,
8660 // etc), just return an empty string.
8661 aDomain.Truncate();
8665 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8666 if (!GetBrowsingContext()) {
8667 // If our browsing context is null; disallow setting domain
8668 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8669 return;
8672 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8673 // We're sandboxed; disallow setting domain
8674 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8675 return;
8678 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8679 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8680 return;
8683 if (aDomain.IsEmpty()) {
8684 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8685 return;
8688 nsCOMPtr<nsIURI> uri = GetDomainURI();
8689 if (!uri) {
8690 rv.Throw(NS_ERROR_FAILURE);
8691 return;
8694 // Check new domain - must be a superdomain of the current host
8695 // For example, a page from foo.bar.com may set domain to bar.com,
8696 // but not to ar.com, baz.com, or fi.foo.bar.com.
8698 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8699 if (!newURI) {
8700 // Error: illegal domain
8701 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8702 return;
8705 if (CrossOriginIsolated()) {
8706 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8707 return;
8710 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
8711 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
8712 WindowGlobalChild* wgc = GetWindowGlobalChild();
8713 if (wgc) {
8714 wgc->SendSetDocumentDomain(newURI);
8718 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
8719 const nsACString& aHostString) {
8720 if (aHostString.IsEmpty()) {
8721 return nullptr;
8724 // Create new URI
8725 nsCOMPtr<nsIURI> uri = GetDomainURI();
8726 if (!uri) {
8727 return nullptr;
8730 nsresult rv;
8731 rv = NS_MutateURI(uri)
8732 .SetUserPass(""_ns)
8733 .SetPort(-1) // we want to reset the port number if needed.
8734 .SetHostPort(aHostString)
8735 .Finalize(uri);
8736 if (NS_FAILED(rv)) {
8737 return nullptr;
8740 return uri.forget();
8743 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
8744 const nsAString& aNewDomain, nsIURI* aOrigHost) {
8745 if (NS_WARN_IF(!aOrigHost)) {
8746 return nullptr;
8749 nsCOMPtr<nsIURI> newURI =
8750 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
8751 if (!newURI) {
8752 // Error: failed to parse input domain
8753 return nullptr;
8756 if (!IsValidDomain(aOrigHost, newURI)) {
8757 // Error: illegal domain
8758 return nullptr;
8761 nsAutoCString domain;
8762 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
8763 return nullptr;
8766 return CreateInheritingURIForHost(domain);
8769 /* static */
8770 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
8771 // Check new domain - must be a superdomain of the current host
8772 // For example, a page from foo.bar.com may set domain to bar.com,
8773 // but not to ar.com, baz.com, or fi.foo.bar.com.
8774 nsAutoCString current;
8775 nsAutoCString domain;
8776 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
8777 current.Truncate();
8779 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
8780 domain.Truncate();
8783 bool ok = current.Equals(domain);
8784 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
8785 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
8786 // We're golden if the new domain is the current page's base domain or a
8787 // subdomain of it.
8788 nsCOMPtr<nsIEffectiveTLDService> tldService =
8789 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
8790 if (!tldService) {
8791 return false;
8794 nsAutoCString currentBaseDomain;
8795 ok = NS_SUCCEEDED(
8796 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
8797 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
8798 (domain.Length() >= currentBaseDomain.Length()),
8799 "uh-oh! slight optimization wasn't valid somehow!");
8800 ok = ok && domain.Length() >= currentBaseDomain.Length();
8803 return ok;
8806 Element* Document::GetHtmlElement() const {
8807 Element* rootElement = GetRootElement();
8808 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
8809 return rootElement;
8810 return nullptr;
8813 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
8814 Element* html = GetHtmlElement();
8815 if (!html) return nullptr;
8817 // Look for the element with aTag inside html. This needs to run
8818 // forwards to find the first such element.
8819 for (nsIContent* child = html->GetFirstChild(); child;
8820 child = child->GetNextSibling()) {
8821 if (child->IsHTMLElement(aTag)) return child->AsElement();
8823 return nullptr;
8826 nsGenericHTMLElement* Document::GetBody() {
8827 Element* html = GetHtmlElement();
8828 if (!html) {
8829 return nullptr;
8832 for (nsIContent* child = html->GetFirstChild(); child;
8833 child = child->GetNextSibling()) {
8834 if (child->IsHTMLElement(nsGkAtoms::body) ||
8835 child->IsHTMLElement(nsGkAtoms::frameset)) {
8836 return static_cast<nsGenericHTMLElement*>(child);
8840 return nullptr;
8843 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
8844 nsCOMPtr<Element> root = GetRootElement();
8846 // The body element must be either a body tag or a frameset tag. And we must
8847 // have a root element to be able to add kids to it.
8848 if (!newBody ||
8849 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
8850 rv.ThrowHierarchyRequestError(
8851 "The new body must be either a body tag or frameset tag.");
8852 return;
8855 if (!root) {
8856 rv.ThrowHierarchyRequestError("No root element.");
8857 return;
8860 // Use DOM methods so that we pass through the appropriate security checks.
8861 nsCOMPtr<Element> currentBody = GetBody();
8862 if (currentBody) {
8863 root->ReplaceChild(*newBody, *currentBody, rv);
8864 } else {
8865 root->AppendChild(*newBody, rv);
8869 HTMLSharedElement* Document::GetHead() {
8870 return static_cast<HTMLSharedElement*>(GetHeadElement());
8873 Element* Document::GetTitleElement() {
8874 // mMayHaveTitleElement will have been set to true if any HTML or SVG
8875 // <title> element has been bound to this document. So if it's false,
8876 // we know there is nothing to do here. This avoids us having to search
8877 // the whole DOM if someone calls document.title on a large document
8878 // without a title.
8879 if (!mMayHaveTitleElement) return nullptr;
8881 Element* root = GetRootElement();
8882 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
8883 // In SVG, the document's title must be a child
8884 for (nsIContent* child = root->GetFirstChild(); child;
8885 child = child->GetNextSibling()) {
8886 if (child->IsSVGElement(nsGkAtoms::title)) {
8887 return child->AsElement();
8890 return nullptr;
8893 // We check the HTML namespace even for non-HTML documents, except SVG. This
8894 // matches the spec and the behavior of all tested browsers.
8895 // We avoid creating a live nsContentList since we don't need to watch for DOM
8896 // tree mutations.
8897 RefPtr<nsContentList> list = new nsContentList(
8898 this, kNameSpaceID_XHTML, nsGkAtoms::title, nsGkAtoms::title,
8899 /* aDeep = */ true,
8900 /* aLiveList = */ false);
8902 nsIContent* first = list->Item(0, false);
8904 return first ? first->AsElement() : nullptr;
8907 void Document::GetTitle(nsAString& aTitle) {
8908 aTitle.Truncate();
8910 Element* rootElement = GetRootElement();
8911 if (!rootElement) {
8912 return;
8915 nsAutoString tmp;
8917 #ifdef MOZ_XUL
8918 if (rootElement->IsXULElement()) {
8919 rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp);
8920 } else
8921 #endif
8923 Element* title = GetTitleElement();
8924 if (!title) {
8925 return;
8927 nsContentUtils::GetNodeTextContent(title, false, tmp);
8930 tmp.CompressWhitespace();
8931 aTitle = tmp;
8934 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
8935 Element* rootElement = GetRootElement();
8936 if (!rootElement) {
8937 return;
8940 #ifdef MOZ_XUL
8941 if (rootElement->IsXULElement()) {
8942 aRv =
8943 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
8944 return;
8946 #endif
8948 Maybe<mozAutoDocUpdate> updateBatch;
8949 nsCOMPtr<Element> title = GetTitleElement();
8950 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
8951 if (!title) {
8952 // Batch updates so that mutation events don't change "the title
8953 // element" under us
8954 updateBatch.emplace(this, true);
8955 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
8956 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
8957 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
8958 NOT_FROM_PARSER);
8959 if (!title) {
8960 return;
8962 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
8963 IgnoreErrors());
8965 } else if (rootElement->IsHTMLElement()) {
8966 if (!title) {
8967 // Batch updates so that mutation events don't change "the title
8968 // element" under us
8969 updateBatch.emplace(this, true);
8970 Element* head = GetHeadElement();
8971 if (!head) {
8972 return;
8975 RefPtr<mozilla::dom::NodeInfo> titleInfo;
8976 titleInfo = mNodeInfoManager->GetNodeInfo(
8977 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
8978 title = NS_NewHTMLTitleElement(titleInfo.forget());
8979 if (!title) {
8980 return;
8983 head->AppendChildTo(title, true, IgnoreErrors());
8985 } else {
8986 return;
8989 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
8992 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
8993 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
8994 "Setting a title while unlinking or destroying the element?");
8995 if (mInUnlinkOrDeletion) {
8996 return;
8999 if (aBoundTitleElement) {
9000 mMayHaveTitleElement = true;
9002 if (mPendingTitleChangeEvent.IsPending()) return;
9004 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9005 RefPtr<nsRunnableMethod<Document, void, false>> event =
9006 NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
9007 &Document::DoNotifyPossibleTitleChange);
9008 nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(event));
9009 if (NS_SUCCEEDED(rv)) {
9010 mPendingTitleChangeEvent = std::move(event);
9014 void Document::DoNotifyPossibleTitleChange() {
9015 if (!mPendingTitleChangeEvent.IsPending()) {
9016 return;
9018 // Make sure the pending runnable method is cleared.
9019 mPendingTitleChangeEvent.Revoke();
9020 mHaveFiredTitleChange = true;
9022 nsAutoString title;
9023 GetTitle(title);
9025 RefPtr<PresShell> presShell = GetPresShell();
9026 if (presShell) {
9027 nsCOMPtr<nsISupports> container =
9028 presShell->GetPresContext()->GetContainerWeak();
9029 if (container) {
9030 nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container);
9031 if (docShellWin) {
9032 docShellWin->SetTitle(title);
9037 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
9038 if (WindowGlobalChild* child = inner->GetWindowGlobalChild()) {
9039 child->SendUpdateDocumentTitle(title);
9043 // Fire a DOM event for the title change.
9044 nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
9045 u"DOMTitleChanged"_ns, CanBubble::eYes,
9046 Cancelable::eYes);
9048 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9049 if (obs) {
9050 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9054 already_AddRefed<MediaQueryList> Document::MatchMedia(
9055 const nsACString& aMediaQueryList, CallerType aCallerType) {
9056 RefPtr<MediaQueryList> result =
9057 new MediaQueryList(this, aMediaQueryList, aCallerType);
9059 mDOMMediaQueryLists.insertBack(result);
9061 return result.forget();
9064 void Document::SetMayStartLayout(bool aMayStartLayout) {
9065 mMayStartLayout = aMayStartLayout;
9066 if (MayStartLayout()) {
9067 // Before starting layout, check whether we're a toplevel chrome
9068 // window. If we are, setup some state so that we don't have to restyle
9069 // the whole tree after StartLayout.
9070 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9071 // We're the chrome document!
9072 win->BeforeStartLayout();
9074 ReadyState state = GetReadyStateEnum();
9075 if (state >= READYSTATE_INTERACTIVE) {
9076 // DOMContentLoaded has fired already.
9077 MaybeResolveReadyForIdle();
9081 MaybeEditingStateChanged();
9084 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9085 mInitializableFrameLoaders.RemoveElement(aLoader);
9086 // Don't even try to initialize.
9087 if (mInDestructor) {
9088 NS_WARNING(
9089 "Trying to initialize a frame loader while"
9090 "document is being deleted");
9091 return NS_ERROR_FAILURE;
9094 mInitializableFrameLoaders.AppendElement(aLoader);
9095 if (!mFrameLoaderRunner) {
9096 mFrameLoaderRunner =
9097 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9098 &Document::MaybeInitializeFinalizeFrameLoaders);
9099 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9100 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9102 return NS_OK;
9105 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9106 nsIRunnable* aFinalizer) {
9107 mInitializableFrameLoaders.RemoveElement(aLoader);
9108 if (mInDestructor) {
9109 return NS_ERROR_FAILURE;
9112 LogRunnable::LogDispatch(aFinalizer);
9113 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9114 if (!mFrameLoaderRunner) {
9115 mFrameLoaderRunner =
9116 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9117 &Document::MaybeInitializeFinalizeFrameLoaders);
9118 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9119 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9121 return NS_OK;
9124 void Document::MaybeInitializeFinalizeFrameLoaders() {
9125 if (mDelayFrameLoaderInitialization) {
9126 // This method will be recalled when !mDelayFrameLoaderInitialization.
9127 mFrameLoaderRunner = nullptr;
9128 return;
9131 // We're not in an update, but it is not safe to run scripts, so
9132 // postpone frameloader initialization and finalization.
9133 if (!nsContentUtils::IsSafeToRunScript()) {
9134 if (!mInDestructor && !mFrameLoaderRunner &&
9135 (mInitializableFrameLoaders.Length() ||
9136 mFrameLoaderFinalizers.Length())) {
9137 mFrameLoaderRunner = NewRunnableMethod(
9138 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9139 &Document::MaybeInitializeFinalizeFrameLoaders);
9140 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9142 return;
9144 mFrameLoaderRunner = nullptr;
9146 // Don't use a temporary array for mInitializableFrameLoaders, because
9147 // loading a frame may cause some other frameloader to be removed from the
9148 // array. But be careful to keep the loader alive when starting the load!
9149 while (mInitializableFrameLoaders.Length()) {
9150 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9151 mInitializableFrameLoaders.RemoveElementAt(0);
9152 NS_ASSERTION(loader, "null frameloader in the array?");
9153 loader->ReallyStartLoading();
9156 uint32_t length = mFrameLoaderFinalizers.Length();
9157 if (length > 0) {
9158 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9159 std::move(mFrameLoaderFinalizers);
9160 for (uint32_t i = 0; i < length; ++i) {
9161 LogRunnable::Run run(finalizers[i]);
9162 finalizers[i]->Run();
9167 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9168 uint32_t length = mInitializableFrameLoaders.Length();
9169 for (uint32_t i = 0; i < length; ++i) {
9170 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9171 mInitializableFrameLoaders.RemoveElementAt(i);
9172 return;
9177 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9178 mPrototypeDocument = aPrototype;
9179 mSynchronousDOMContentLoaded = true;
9182 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9183 return GetPermissionDelegateHandler();
9186 Document* Document::RequestExternalResource(
9187 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9188 ExternalResourceLoad** aPendingLoad) {
9189 MOZ_ASSERT(aURI, "Must have a URI");
9190 MOZ_ASSERT(aRequestingNode, "Must have a node");
9191 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9192 if (mDisplayDocument) {
9193 return mDisplayDocument->RequestExternalResource(
9194 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9197 return mExternalResourceMap.RequestResource(
9198 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9201 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9202 mExternalResourceMap.EnumerateResources(aCallback);
9205 SMILAnimationController* Document::GetAnimationController() {
9206 // We create the animation controller lazily because most documents won't want
9207 // one and only SVG documents and the like will call this
9208 if (mAnimationController) return mAnimationController;
9209 // Refuse to create an Animation Controller for data documents.
9210 if (mLoadedAsData) return nullptr;
9212 mAnimationController = new SMILAnimationController(this);
9214 // If there's a presContext then check the animation mode and pause if
9215 // necessary.
9216 nsPresContext* context = GetPresContext();
9217 if (mAnimationController && context &&
9218 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9219 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9222 // If we're hidden (or being hidden), notify the newly-created animation
9223 // controller. (Skip this check for SVG-as-an-image documents, though,
9224 // because they don't get OnPageShow / OnPageHide calls).
9225 if (!mIsShowing && !mIsBeingUsedAsImage) {
9226 mAnimationController->OnPageHide();
9229 return mAnimationController;
9232 PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
9233 if (!mPendingAnimationTracker) {
9234 mPendingAnimationTracker = new PendingAnimationTracker(this);
9237 return mPendingAnimationTracker;
9241 * Retrieve the "direction" property of the document.
9243 * @lina 01/09/2001
9245 void Document::GetDir(nsAString& aDirection) const {
9246 aDirection.Truncate();
9247 Element* rootElement = GetHtmlElement();
9248 if (rootElement) {
9249 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9254 * Set the "direction" property of the document.
9256 * @lina 01/09/2001
9258 void Document::SetDir(const nsAString& aDirection) {
9259 Element* rootElement = GetHtmlElement();
9260 if (rootElement) {
9261 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9265 nsIHTMLCollection* Document::Images() {
9266 if (!mImages) {
9267 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9268 nsGkAtoms::img);
9270 return mImages;
9273 nsIHTMLCollection* Document::Embeds() {
9274 if (!mEmbeds) {
9275 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9276 nsGkAtoms::embed);
9278 return mEmbeds;
9281 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9282 void* aData) {
9283 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9284 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
9287 nsIHTMLCollection* Document::Links() {
9288 if (!mLinks) {
9289 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9291 return mLinks;
9294 nsIHTMLCollection* Document::Forms() {
9295 if (!mForms) {
9296 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9297 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9298 nsGkAtoms::form);
9301 return mForms;
9304 nsIHTMLCollection* Document::Scripts() {
9305 if (!mScripts) {
9306 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9307 nsGkAtoms::script);
9309 return mScripts;
9312 nsIHTMLCollection* Document::Applets() {
9313 if (!mApplets) {
9314 mApplets = new nsEmptyContentList(this);
9316 return mApplets;
9319 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9320 void* aData) {
9321 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9322 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
9325 nsIHTMLCollection* Document::Anchors() {
9326 if (!mAnchors) {
9327 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9329 return mAnchors;
9332 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9333 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9334 ErrorResult& rv) {
9335 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9336 "XOW should have caught this!");
9338 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9339 if (!window) {
9340 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9341 return nullptr;
9343 nsCOMPtr<nsPIDOMWindowOuter> outer =
9344 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9345 if (!outer) {
9346 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9347 return nullptr;
9349 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9350 RefPtr<BrowsingContext> newBC;
9351 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9352 if (!newBC) {
9353 return nullptr;
9355 return WindowProxyHolder(std::move(newBC));
9358 Document* Document::Open(const Optional<nsAString>& /* unused */,
9359 const Optional<nsAString>& /* unused */,
9360 ErrorResult& aError) {
9361 // Implements
9362 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9364 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9365 "XOW should have caught this!");
9367 // Step 1 -- throw if we're an XML document.
9368 if (!IsHTMLDocument() || mDisableDocWrite) {
9369 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9370 return nullptr;
9373 // Step 2 -- throw if dynamic markup insertion should throw.
9374 if (ShouldThrowOnDynamicMarkupInsertion()) {
9375 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9376 return nullptr;
9379 // Step 3 -- get the entry document, so we can use it for security checks.
9380 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9381 if (!callerDoc) {
9382 // If we're called from C++ or in some other way without an originating
9383 // document we can't do a document.open w/o changing the principal of the
9384 // document to something like about:blank (as that's the only sane thing to
9385 // do when we don't know the origin of this call), and since we can't
9386 // change the principals of a document for security reasons we'll have to
9387 // refuse to go ahead with this call.
9389 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9390 return nullptr;
9393 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9394 // the entry document.
9395 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9396 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9397 return nullptr;
9400 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9401 // just no-op.
9402 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9403 return this;
9406 // Step 6 -- check for open() during unload. Per spec, this is just a check
9407 // of the ignore-opens-during-unload counter, but our unload event code
9408 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9409 // do), so we check for unload directly.
9410 if (ShouldIgnoreOpens()) {
9411 return this;
9414 RefPtr<nsDocShell> shell(mDocumentContainer);
9415 if (shell) {
9416 bool inUnload;
9417 shell->GetIsInUnload(&inUnload);
9418 if (inUnload) {
9419 return this;
9423 // document.open() inherits the CSP from the opening document.
9424 // Please create an actual copy of the CSP (do not share the same
9425 // reference) otherwise appending a new policy within the opened
9426 // document will be incorrectly propagated to the opening doc.
9427 nsCOMPtr<nsIContentSecurityPolicy> csp = callerDoc->GetCsp();
9428 if (csp) {
9429 RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
9430 cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
9431 mCSP = cspToInherit;
9434 // At this point we know this is a valid-enough document.open() call
9435 // and not a no-op. Increment our use counter.
9436 SetUseCounter(eUseCounter_custom_DocumentOpen);
9438 // Step 7 -- stop existing navigation of our browsing context (and all other
9439 // loads it's doing) if we're the active document of our browsing context.
9440 // Note that we do not want to stop anything if there is no existing
9441 // navigation.
9442 if (shell && IsCurrentActiveDocument() &&
9443 shell->GetIsAttemptingToNavigate()) {
9444 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9446 // The Stop call may have cancelled the onload blocker request or
9447 // prevented it from getting added, so we need to make sure it gets added
9448 // to the document again otherwise the document could have a non-zero
9449 // onload block count without the onload blocker request being in the
9450 // loadgroup.
9451 EnsureOnloadBlocker();
9454 // Step 8 -- clear event listeners out of our DOM tree
9455 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9456 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9457 elm->RemoveAllListeners();
9461 // Step 9 -- clear event listeners from our window, if we have one.
9463 // Note that we explicitly want the inner window, and only if we're its
9464 // document. We want to do this (per spec) even when we're not the "active
9465 // document", so we can't go through GetWindow(), because it might forward to
9466 // the wrong inner.
9467 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9468 if (win->GetExtantDoc() == this) {
9469 if (EventListenerManager* elm =
9470 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9471 elm->RemoveAllListeners();
9476 // If we have a parser that has a zero script nesting level, we need to
9477 // properly terminate it. We do that after we've removed all the event
9478 // listeners (so termination won't trigger event listeners if it does
9479 // something to the DOM), but before we remove all elements from the document
9480 // (so if termination does modify the DOM in some way we will just blow it
9481 // away immediately. See the similar code in WriteCommon that handles the
9482 // !IsInsertionPointDefined() case and should stay in sync with this code.
9483 if (mParser) {
9484 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9485 "Why didn't we take the early return?");
9486 // Make sure we don't re-enter.
9487 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9488 mParser->Terminate();
9489 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9492 // Step 10 -- remove all our DOM kids without firing any mutation events.
9494 // We want to ignore any recursive calls to Open() that happen while
9495 // disconnecting the node tree. The spec doesn't say to do this, but the
9496 // spec also doesn't envision unload events on subframes firing while we do
9497 // this, while all browsers fire them in practice. See
9498 // <https://github.com/whatwg/html/issues/4611>.
9499 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9500 DisconnectNodeTree();
9503 // Step 11 -- if we're the current document in our docshell, do the
9504 // equivalent of pushState() with the new URL we should have.
9505 if (shell && IsCurrentActiveDocument()) {
9506 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9507 if (callerDoc != this) {
9508 nsCOMPtr<nsIURI> noFragmentURI;
9509 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9510 if (NS_WARN_IF(NS_FAILED(rv))) {
9511 aError.Throw(rv);
9512 return nullptr;
9514 newURI = std::move(noFragmentURI);
9517 // UpdateURLAndHistory might do various member-setting, so make sure we're
9518 // holding strong refs to all the refcounted args on the stack. We can
9519 // assume that our caller is holding on to "this" already.
9520 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9521 bool equalURIs;
9522 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9523 if (NS_WARN_IF(NS_FAILED(rv))) {
9524 aError.Throw(rv);
9525 return nullptr;
9527 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9528 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9529 /* aReplace = */ true, currentURI,
9530 equalURIs);
9531 if (NS_WARN_IF(NS_FAILED(rv))) {
9532 aError.Throw(rv);
9533 return nullptr;
9536 // And use the security info of the caller document as well, since
9537 // it's the thing providing our data.
9538 mSecurityInfo = callerDoc->GetSecurityInfo();
9540 // This is not mentioned in the spec, but I think that's a spec bug. See
9541 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9542 // URL may be changing away from about:blank here, we really want to unset
9543 // this flag no matter what, since only about:blank can be an initial
9544 // document.
9545 SetIsInitialDocument(false);
9547 // And let our docloader know that it will need to track our load event.
9548 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9551 // Per spec nothing happens with our URI in other cases, though note
9552 // <https://github.com/whatwg/html/issues/4286>.
9554 // Note that we don't need to do anything here with base URIs per spec.
9555 // That said, this might be assuming that we implement
9556 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9557 // correctly, which we don't right now for the about:blank case.
9559 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9560 mSkipLoadEventAfterClose = mLoadEventFiring;
9562 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9563 // we do anything else, so we can then proceed to later ready state levels.
9564 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9565 /* updateTimingInformation = */ false);
9566 // Reset a flag that affects readyState behavior.
9567 mSetCompleteAfterDOMContentLoaded = false;
9569 // Step 13 -- set our compat mode to standards.
9570 SetCompatibilityMode(eCompatibility_FullStandards);
9572 // Step 14 -- create a new parser associated with document. This also does
9573 // step 16 implicitly.
9574 mParserAborted = false;
9575 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9576 mParser = parser;
9577 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9578 nsresult rv = parser->StartExecutor();
9579 if (NS_WARN_IF(NS_FAILED(rv))) {
9580 aError.Throw(rv);
9581 return nullptr;
9584 // Clear out our form control state, because the state of controls
9585 // in the pre-open() document should not affect the state of
9586 // controls that are now going to be written.
9587 mLayoutHistoryState = nullptr;
9589 if (shell) {
9590 // Prepare the docshell and the document viewer for the impending
9591 // out-of-band document.write()
9592 shell->PrepareForNewContentModel();
9594 nsCOMPtr<nsIContentViewer> cv;
9595 shell->GetContentViewer(getter_AddRefs(cv));
9596 if (cv) {
9597 cv->LoadStart(this);
9601 // Step 15.
9602 SetReadyStateInternal(Document::READYSTATE_LOADING,
9603 /* updateTimingInformation = */ false);
9605 // Step 16 happened with step 14 above.
9607 // Step 17.
9608 return this;
9611 void Document::Close(ErrorResult& rv) {
9612 if (!IsHTMLDocument()) {
9613 // No calling document.close() on XHTML!
9615 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9616 return;
9619 if (ShouldThrowOnDynamicMarkupInsertion()) {
9620 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9621 return;
9624 if (!mParser || !mParser->IsScriptCreated()) {
9625 return;
9628 ++mWriteLevel;
9629 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9630 ->Parse(u""_ns, nullptr, true);
9631 --mWriteLevel;
9634 void Document::WriteCommon(const Sequence<nsString>& aText,
9635 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9636 // Fast path the common case
9637 if (aText.Length() == 1) {
9638 WriteCommon(aText[0], aNewlineTerminate, rv);
9639 } else {
9640 // XXXbz it would be nice if we could pass all the strings to the parser
9641 // without having to do all this copying and then ask it to start
9642 // parsing....
9643 nsString text;
9644 for (size_t i = 0; i < aText.Length(); ++i) {
9645 text.Append(aText[i]);
9647 WriteCommon(text, aNewlineTerminate, rv);
9651 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9652 ErrorResult& aRv) {
9653 #ifdef DEBUG
9655 // Assert that we do not use or accidentally introduce doc.write()
9656 // in system privileged context or in any of our about: pages.
9657 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9658 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9659 if (!isAboutOrPrivContext) {
9660 if (principal->SchemeIs("about")) {
9661 // about:blank inherits the security contetext and this assertion
9662 // is only meant for actual about: pages.
9663 nsAutoCString host;
9664 principal->GetHost(host);
9665 isAboutOrPrivContext = !host.EqualsLiteral("blank");
9668 // Some automated tests use an empty string to kick off some parsing
9669 // mechansims, but they do not do any harm since they use an empty string.
9670 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
9671 "do not use doc.write in privileged context!");
9673 #endif
9675 mTooDeepWriteRecursion =
9676 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9677 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9678 aRv.Throw(NS_ERROR_UNEXPECTED);
9679 return;
9682 if (!IsHTMLDocument() || mDisableDocWrite) {
9683 // No calling document.write*() on XHTML!
9685 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9686 return;
9689 if (ShouldThrowOnDynamicMarkupInsertion()) {
9690 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9691 return;
9694 if (mParserAborted) {
9695 // Hixie says aborting the parser doesn't undefine the insertion point.
9696 // However, since we null out mParser in that case, we track the
9697 // theoretically defined insertion point using mParserAborted.
9698 return;
9701 // Implement Step 4.1 of:
9702 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
9703 if (ShouldIgnoreOpens()) {
9704 return;
9707 void* key = GenerateParserKey();
9708 if (mParser && !mParser->IsInsertionPointDefined()) {
9709 if (mIgnoreDestructiveWritesCounter) {
9710 // Instead of implying a call to document.open(), ignore the call.
9711 nsContentUtils::ReportToConsole(
9712 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9713 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9714 return;
9716 // The spec doesn't tell us to ignore opens from here, but we need to
9717 // ensure opens are ignored here. See similar code in Open() that handles
9718 // the case of an existing parser which is not currently running script and
9719 // should stay in sync with this code.
9720 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9721 mParser->Terminate();
9722 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9725 if (!mParser) {
9726 if (mIgnoreDestructiveWritesCounter) {
9727 // Instead of implying a call to document.open(), ignore the call.
9728 nsContentUtils::ReportToConsole(
9729 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9730 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9731 return;
9734 Open({}, {}, aRv);
9736 // If Open() fails, or if it didn't create a parser (as it won't
9737 // if the user chose to not discard the current document through
9738 // onbeforeunload), don't write anything.
9739 if (aRv.Failed() || !mParser) {
9740 return;
9744 static constexpr auto new_line = u"\n"_ns;
9746 ++mWriteLevel;
9748 // This could be done with less code, but for performance reasons it
9749 // makes sense to have the code for two separate Parse() calls here
9750 // since the concatenation of strings costs more than we like. And
9751 // why pay that price when we don't need to?
9752 if (aNewlineTerminate) {
9753 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
9754 ->Parse(aText + new_line, key, false);
9755 } else {
9756 aRv =
9757 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
9760 --mWriteLevel;
9762 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
9765 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
9766 WriteCommon(aText, false, rv);
9769 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
9770 WriteCommon(aText, true, rv);
9773 void* Document::GenerateParserKey(void) {
9774 if (!mScriptLoader) {
9775 // If we don't have a script loader, then the parser probably isn't parsing
9776 // anything anyway, so just return null.
9777 return nullptr;
9780 // The script loader provides us with the currently executing script element,
9781 // which is guaranteed to be unique per script.
9782 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
9783 if (script && mParser && mParser->IsScriptCreated()) {
9784 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
9785 if (creatorParser != mParser) {
9786 // Make scripts that aren't inserted by the active parser of this document
9787 // participate in the context of the script that document.open()ed
9788 // this document.
9789 return nullptr;
9792 return script;
9795 /* static */
9796 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
9797 nsAtom* aAtom, void* aData) {
9798 MOZ_ASSERT(aElement, "Must have element to work with!");
9800 if (!aElement->HasName()) {
9801 return false;
9804 nsString* elementName = static_cast<nsString*>(aData);
9805 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
9806 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
9807 eCaseMatters);
9810 /* static */
9811 void* Document::UseExistingNameString(nsINode* aRootNode,
9812 const nsString* aName) {
9813 return const_cast<nsString*>(aName);
9816 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
9817 if (mDocumentURI) {
9818 nsAutoCString uri;
9819 nsresult rv = mDocumentURI->GetSpec(uri);
9820 NS_ENSURE_SUCCESS(rv, rv);
9822 CopyUTF8toUTF16(uri, aDocumentURI);
9823 } else {
9824 aDocumentURI.Truncate();
9827 return NS_OK;
9830 // Alias of above
9831 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
9833 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
9834 CallerType aCallerType,
9835 ErrorResult& aRv) const {
9836 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
9837 aRv = GetDocumentURI(aDocumentURI);
9838 return;
9841 nsAutoCString uri;
9842 nsresult res = mChromeXHRDocURI->GetSpec(uri);
9843 if (NS_FAILED(res)) {
9844 aRv.Throw(res);
9845 return;
9847 CopyUTF8toUTF16(uri, aDocumentURI);
9850 nsIURI* Document::GetDocumentURIObject() const {
9851 if (!mChromeXHRDocURI) {
9852 return GetDocumentURI();
9855 return mChromeXHRDocURI;
9858 void Document::GetCompatMode(nsString& aCompatMode) const {
9859 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
9860 mCompatMode == eCompatibility_AlmostStandards ||
9861 mCompatMode == eCompatibility_FullStandards,
9862 "mCompatMode is neither quirks nor strict for this document");
9864 if (mCompatMode == eCompatibility_NavQuirks) {
9865 aCompatMode.AssignLiteral("BackCompat");
9866 } else {
9867 aCompatMode.AssignLiteral("CSS1Compat");
9871 } // namespace dom
9872 } // namespace mozilla
9874 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
9875 if (Element* element = Element::FromNode(aNode)) {
9876 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
9877 while (true) {
9878 RefPtr<Attr> attr;
9880 // Use an iterator to get an arbitrary attribute from the
9881 // cache. The iterator must be destroyed before any other
9882 // operations on mAttributeCache, to avoid hash table
9883 // assertions.
9884 auto iter = map->mAttributeCache.ConstIter();
9885 if (iter.Done()) {
9886 break;
9888 attr = iter.UserData();
9891 BlastSubtreeToPieces(attr);
9893 mozilla::DebugOnly<nsresult> rv =
9894 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
9895 attr->NodeInfo()->NameAtom(), false);
9897 // XXX Should we abort here?
9898 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
9902 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
9903 BlastSubtreeToPieces(shadow);
9904 element->UnattachShadow();
9908 while (aNode->HasChildren()) {
9909 nsIContent* node = aNode->GetFirstChild();
9910 BlastSubtreeToPieces(node);
9911 aNode->RemoveChildNode(node, false);
9915 namespace mozilla::dom {
9917 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) {
9918 nsINode* adoptedNode = &aAdoptedNode;
9920 // Scope firing mutation events so that we don't carry any state that
9921 // might be stale
9923 if (nsINode* parent = adoptedNode->GetParentNode()) {
9924 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
9928 nsAutoScriptBlocker scriptBlocker;
9930 switch (adoptedNode->NodeType()) {
9931 case ATTRIBUTE_NODE: {
9932 // Remove from ownerElement.
9933 RefPtr<Attr> adoptedAttr = static_cast<Attr*>(adoptedNode);
9935 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement(rv);
9936 if (rv.Failed()) {
9937 return nullptr;
9940 if (ownerElement) {
9941 RefPtr<Attr> newAttr =
9942 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
9943 if (rv.Failed()) {
9944 return nullptr;
9947 newAttr.swap(adoptedAttr);
9950 break;
9952 case DOCUMENT_FRAGMENT_NODE: {
9953 if (adoptedNode->IsShadowRoot()) {
9954 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
9955 return nullptr;
9957 [[fallthrough]];
9959 case ELEMENT_NODE:
9960 case PROCESSING_INSTRUCTION_NODE:
9961 case TEXT_NODE:
9962 case CDATA_SECTION_NODE:
9963 case COMMENT_NODE:
9964 case DOCUMENT_TYPE_NODE: {
9965 // Don't allow adopting a node's anonymous subtree out from under it.
9966 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
9967 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
9968 return nullptr;
9971 // We don't want to adopt an element into its own contentDocument or into
9972 // a descendant contentDocument, so we check if the frameElement of this
9973 // document or any of its parents is the adopted node or one of its
9974 // descendants.
9975 RefPtr<BrowsingContext> bc = GetBrowsingContext();
9976 while (bc) {
9977 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
9978 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
9979 rv.ThrowHierarchyRequestError(
9980 "Trying to adopt a node into its own contentDocument or a "
9981 "descendant contentDocument.");
9982 return nullptr;
9985 if (XRE_IsParentProcess()) {
9986 bc = bc->Canonical()->GetParentCrossChromeBoundary();
9987 } else {
9988 bc = bc->GetParent();
9992 // Remove from parent.
9993 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
9994 if (parent) {
9995 parent->RemoveChildNode(adoptedNode->AsContent(), true);
9996 } else {
9997 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10000 break;
10002 case DOCUMENT_NODE: {
10003 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10004 return nullptr;
10006 default: {
10007 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10009 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10010 return nullptr;
10014 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10015 bool sameDocument = oldDocument == this;
10017 AutoJSContext cx;
10018 JS::Rooted<JSObject*> newScope(cx, nullptr);
10019 if (!sameDocument) {
10020 newScope = GetWrapper();
10021 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10022 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10023 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10024 // false, and documents should always insist on being wrapped in an
10025 // canonical scope. But we try to pass something sane anyway.
10026 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10027 JSAutoRealm ar(cx, globalObject);
10028 JS::Rooted<JS::Value> v(cx);
10029 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10030 /* aAllowWrapping = */ false);
10031 if (rv.Failed()) return nullptr;
10032 newScope = &v.toObject();
10036 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10037 if (rv.Failed()) {
10038 // Disconnect all nodes from their parents, since some have the old document
10039 // as their ownerDocument and some have this as their ownerDocument.
10040 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10041 return nullptr;
10044 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10045 "Should still be in the document we just got adopted into");
10047 return adoptedNode;
10050 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10052 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10053 const nsString& aScaleString) {
10054 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10055 if (aScaleString.EqualsLiteral("device-width") ||
10056 aScaleString.EqualsLiteral("device-height")) {
10057 return Some(LayoutDeviceToScreenScale(10.0f));
10058 } else if (aScaleString.EqualsLiteral("yes")) {
10059 return Some(LayoutDeviceToScreenScale(1.0f));
10060 } else if (aScaleString.EqualsLiteral("no")) {
10061 return Some(LayoutDeviceToScreenScale(kViewportMinScale));
10062 } else if (aScaleString.IsEmpty()) {
10063 return Nothing();
10066 nsresult scaleErrorCode;
10067 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10068 if (NS_FAILED(scaleErrorCode)) {
10069 return Some(LayoutDeviceToScreenScale(kViewportMinScale));
10072 if (scale < 0) {
10073 return Nothing();
10075 return Some(clamped(LayoutDeviceToScreenScale(scale), kViewportMinScale,
10076 kViewportMaxScale));
10079 void Document::ParseScalesInViewportMetaData(
10080 const ViewportMetaData& aViewportMetaData) {
10081 Maybe<LayoutDeviceToScreenScale> scale;
10083 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10084 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10085 mValidScaleFloat = scale.isSome();
10087 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10088 // Chrome uses '5' for the fallback value of maximum-scale, we might
10089 // consider matching it in future.
10090 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10091 mScaleMaxFloat = scale.valueOr(kViewportMaxScale);
10092 mValidMaxScale = scale.isSome();
10094 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10095 mScaleMinFloat = scale.valueOr(kViewportMinScale);
10096 mValidMinScale = scale.isSome();
10098 // Resolve min-zoom and max-zoom values.
10099 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10100 if (mValidMaxScale && mValidMinScale) {
10101 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10105 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10106 const nsAString& aHeightString,
10107 bool aHasValidScale) {
10108 // The width and height properties
10109 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10111 // The width and height viewport <META> properties are translated into width
10112 // and height descriptors, setting the min-width/min-height value to
10113 // extend-to-zoom and the max-width/max-height value to the length from the
10114 // viewport <META> property as follows:
10116 // 1. Non-negative number values are translated to pixel lengths, clamped to
10117 // the range: [1px, 10000px]
10118 // 2. Negative number values are dropped
10119 // 3. device-width and device-height translate to 100vw and 100vh respectively
10120 // 4. Other keywords and unknown values are also dropped
10121 mMinWidth = nsViewportInfo::Auto;
10122 mMaxWidth = nsViewportInfo::Auto;
10123 if (!aWidthString.IsEmpty()) {
10124 mMinWidth = nsViewportInfo::ExtendToZoom;
10125 if (aWidthString.EqualsLiteral("device-width")) {
10126 mMaxWidth = nsViewportInfo::DeviceSize;
10127 } else {
10128 nsresult widthErrorCode;
10129 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10130 if (NS_FAILED(widthErrorCode)) {
10131 mMaxWidth = nsViewportInfo::Auto;
10132 } else if (mMaxWidth >= 0.0f) {
10133 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10134 } else {
10135 mMaxWidth = nsViewportInfo::Auto;
10138 } else if (aHasValidScale) {
10139 if (aHeightString.IsEmpty()) {
10140 mMinWidth = nsViewportInfo::ExtendToZoom;
10141 mMaxWidth = nsViewportInfo::ExtendToZoom;
10143 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10144 mMinWidth = nsViewportInfo::ExtendToZoom;
10145 mMaxWidth = nsViewportInfo::DeviceSize;
10148 mMinHeight = nsViewportInfo::Auto;
10149 mMaxHeight = nsViewportInfo::Auto;
10150 if (!aHeightString.IsEmpty()) {
10151 mMinHeight = nsViewportInfo::ExtendToZoom;
10152 if (aHeightString.EqualsLiteral("device-height")) {
10153 mMaxHeight = nsViewportInfo::DeviceSize;
10154 } else {
10155 nsresult heightErrorCode;
10156 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10157 if (NS_FAILED(heightErrorCode)) {
10158 mMaxHeight = nsViewportInfo::Auto;
10159 } else if (mMaxHeight >= 0.0f) {
10160 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10161 } else {
10162 mMaxHeight = nsViewportInfo::Auto;
10168 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10169 MOZ_ASSERT(mPresShell);
10171 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10172 // widget scale and the full zoom.
10173 nsPresContext* context = mPresShell->GetPresContext();
10174 // When querying the full zoom, get it from the device context rather than
10175 // directly from the pres context, because the device context's value can
10176 // include an adjustment necessay to keep the number of app units per device
10177 // pixel an integer, and we want the adjusted value.
10178 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10179 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10180 CSSToLayoutDeviceScale layoutDeviceScale =
10181 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10183 CSSToScreenScale defaultScale =
10184 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10186 // Special behaviour for desktop mode, provided we are not on an about: page,
10187 // or fullscreen.
10188 const bool fullscreen = Fullscreen();
10189 nsPIDOMWindowOuter* win = GetWindow();
10190 if (win && win->IsDesktopModeViewport() && !IsAboutPage() && !fullscreen) {
10191 CSSCoord viewportWidth =
10192 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10193 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10194 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10195 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10196 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10197 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10198 nsViewportInfo::ZoomFlag::AllowZoom,
10199 nsViewportInfo::ZoomBehaviour::Mobile);
10202 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10203 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10204 return nsViewportInfo(aDisplaySize, defaultScale,
10205 nsLayoutUtils::AllowZoomingForDocument(this)
10206 ? nsViewportInfo::ZoomFlag::AllowZoom
10207 : nsViewportInfo::ZoomFlag::DisallowZoom,
10208 StaticPrefs::apz_allow_zooming_out()
10209 ? nsViewportInfo::ZoomBehaviour::Mobile
10210 : nsViewportInfo::ZoomBehaviour::Desktop);
10213 // In cases where the width of the CSS viewport is less than or equal to the
10214 // width of the display (i.e. width <= device-width) then we disable
10215 // double-tap-to-zoom behaviour. See bug 941995 for details.
10217 switch (mViewportType) {
10218 case DisplayWidthHeight:
10219 return nsViewportInfo(aDisplaySize, defaultScale,
10220 nsViewportInfo::ZoomFlag::AllowZoom,
10221 nsViewportInfo::ZoomBehaviour::Mobile);
10222 case Unknown: {
10223 nsAutoString viewport;
10224 GetHeaderData(nsGkAtoms::viewport, viewport);
10225 // We might early exit if the viewport is empty. Even if we don't,
10226 // at the end of this case we'll note that it was empty. Later, when
10227 // we're using the cached values, this will trigger alternate code paths.
10228 if (viewport.IsEmpty()) {
10229 // If the docType specifies that we are on a site optimized for mobile,
10230 // then we want to return specially crafted defaults for the viewport
10231 // info.
10232 RefPtr<DocumentType> docType = GetDoctype();
10233 if (docType) {
10234 nsAutoString docId;
10235 docType->GetPublicId(docId);
10236 if ((docId.Find("WAP") != -1) || (docId.Find("Mobile") != -1) ||
10237 (docId.Find("WML") != -1)) {
10238 // We're making an assumption that the docType can't change here
10239 mViewportType = DisplayWidthHeight;
10240 return nsViewportInfo(aDisplaySize, defaultScale,
10241 nsViewportInfo::ZoomFlag::AllowZoom,
10242 nsViewportInfo::ZoomBehaviour::Mobile);
10246 nsAutoString handheldFriendly;
10247 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10248 if (handheldFriendly.EqualsLiteral("true")) {
10249 mViewportType = DisplayWidthHeight;
10250 return nsViewportInfo(aDisplaySize, defaultScale,
10251 nsViewportInfo::ZoomFlag::AllowZoom,
10252 nsViewportInfo::ZoomBehaviour::Mobile);
10256 ViewportMetaData metaData = GetViewportMetaData();
10258 // Parse initial-scale, minimum-scale and maximum-scale.
10259 ParseScalesInViewportMetaData(metaData);
10261 // Parse width and height properties
10262 // This function sets m{Min,Max}{Width,Height}.
10263 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10264 mValidScaleFloat);
10266 mAllowZoom = true;
10267 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10268 (metaData.mUserScalable.EqualsLiteral("no")) ||
10269 (metaData.mUserScalable.EqualsLiteral("false"))) {
10270 mAllowZoom = false;
10273 // Resolve viewport-fit value.
10274 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10275 mViewportFit = ViewportFitType::Auto;
10276 if (!metaData.mViewportFit.IsEmpty()) {
10277 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10278 mViewportFit = ViewportFitType::Contain;
10279 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10280 mViewportFit = ViewportFitType::Cover;
10284 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10286 mViewportType = Specified;
10287 [[fallthrough]];
10289 case Specified:
10290 default:
10291 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10292 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10293 bool effectiveValidMaxScale = mValidMaxScale;
10295 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10296 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10297 : nsViewportInfo::ZoomFlag::DisallowZoom;
10298 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10299 // If the pref to force user-scalable is enabled, we ignore the values
10300 // from the meta-viewport tag for these properties and just assume they
10301 // allow the page to be scalable. Note in particular that this code is
10302 // in the "Specified" branch of the enclosing switch statement, so that
10303 // calls to GetViewportInfo always use the latest value of the
10304 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10305 // return nsViewportInfo instances are all consistent with
10306 // browser_ui_zoom_force_user_scalable() already.
10307 effectiveMinScale = kViewportMinScale;
10308 effectiveMaxScale = kViewportMaxScale;
10309 effectiveValidMaxScale = true;
10310 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10313 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10314 auto ComputeExtendZoom = [&]() -> float {
10315 if (mValidScaleFloat && effectiveValidMaxScale) {
10316 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10318 if (mValidScaleFloat) {
10319 return mScaleFloat.scale;
10321 if (effectiveValidMaxScale) {
10322 return effectiveMaxScale.scale;
10324 return nsViewportInfo::Auto;
10327 // Resolving 'extend-to-zoom'
10328 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10329 float extendZoom = ComputeExtendZoom();
10331 CSSCoord minWidth = mMinWidth;
10332 CSSCoord maxWidth = mMaxWidth;
10333 CSSCoord minHeight = mMinHeight;
10334 CSSCoord maxHeight = mMaxHeight;
10336 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10337 // viewport size. We need to use this scaled size for any clamping of
10338 // width or height.
10339 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10341 // Our min and max width and height values are mostly as specified by
10342 // the viewport declaration, but we make an exception for max width.
10343 // Max width, if auto, and if there's no initial-scale, will be set
10344 // to a default size. This is to support legacy site design with no
10345 // viewport declaration, and to do that using the same scheme as
10346 // Chrome does, in order to maintain web compatibility. Since the
10347 // default size has a complicated calculation, we fixup the maxWidth
10348 // value after setting it, above.
10349 if (maxWidth == nsViewportInfo::Auto && !mValidScaleFloat) {
10350 BrowsingContext* bc = GetBrowsingContext();
10351 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10352 bc->InRDMPane()) {
10353 // If RDM and touch simulation are active, then use the simulated
10354 // screen width to accomodate for cases where the screen width is
10355 // larger than the desktop viewport default.
10356 maxWidth = nsViewportInfo::Max(
10357 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10358 } else {
10359 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10361 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10362 // to keep device pixel size unchanged after full zoom applied.
10363 // See bug 974242.
10364 maxWidth /= fullZoom;
10366 // We set minWidth to ExtendToZoom, which will cause our later width
10367 // calculation to expand to maxWidth, if scale restrictions allow it.
10368 minWidth = nsViewportInfo::ExtendToZoom;
10371 // Resolve device-width and device-height first.
10372 if (maxWidth == nsViewportInfo::DeviceSize) {
10373 maxWidth = displaySize.width;
10375 if (maxHeight == nsViewportInfo::DeviceSize) {
10376 maxHeight = displaySize.height;
10378 if (extendZoom == nsViewportInfo::Auto) {
10379 if (maxWidth == nsViewportInfo::ExtendToZoom) {
10380 maxWidth = nsViewportInfo::Auto;
10382 if (maxHeight == nsViewportInfo::ExtendToZoom) {
10383 maxHeight = nsViewportInfo::Auto;
10385 if (minWidth == nsViewportInfo::ExtendToZoom) {
10386 minWidth = maxWidth;
10388 if (minHeight == nsViewportInfo::ExtendToZoom) {
10389 minHeight = maxHeight;
10391 } else {
10392 CSSSize extendSize = displaySize / extendZoom;
10393 if (maxWidth == nsViewportInfo::ExtendToZoom) {
10394 maxWidth = extendSize.width;
10396 if (maxHeight == nsViewportInfo::ExtendToZoom) {
10397 maxHeight = extendSize.height;
10399 if (minWidth == nsViewportInfo::ExtendToZoom) {
10400 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10402 if (minHeight == nsViewportInfo::ExtendToZoom) {
10403 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10407 // Resolve initial width and height from min/max descriptors
10408 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10409 CSSCoord width = nsViewportInfo::Auto;
10410 if (minWidth != nsViewportInfo::Auto ||
10411 maxWidth != nsViewportInfo::Auto) {
10412 width = nsViewportInfo::Max(
10413 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10415 CSSCoord height = nsViewportInfo::Auto;
10416 if (minHeight != nsViewportInfo::Auto ||
10417 maxHeight != nsViewportInfo::Auto) {
10418 height = nsViewportInfo::Max(
10419 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10422 // Resolve width value
10423 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10424 if (width == nsViewportInfo::Auto) {
10425 if (height == nsViewportInfo::Auto || aDisplaySize.height == 0) {
10426 width = displaySize.width;
10427 } else {
10428 width = height * aDisplaySize.width / aDisplaySize.height;
10432 // Resolve height value
10433 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10434 if (height == nsViewportInfo::Auto) {
10435 if (aDisplaySize.width == 0) {
10436 height = displaySize.height;
10437 } else {
10438 height = width * aDisplaySize.height / aDisplaySize.width;
10441 MOZ_ASSERT(width != nsViewportInfo::Auto &&
10442 height != nsViewportInfo::Auto);
10444 CSSSize size(width, height);
10446 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10447 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10448 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10450 nsViewportInfo::AutoSizeFlag sizeFlag =
10451 nsViewportInfo::AutoSizeFlag::FixedSize;
10452 if (mMaxWidth == nsViewportInfo::DeviceSize ||
10453 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::DeviceSize ||
10454 mScaleFloat.scale == 1.0f)) ||
10455 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::Auto &&
10456 mMaxHeight < 0)) {
10457 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10460 // FIXME: Resolving width and height should be done above 'Resolve width
10461 // value' and 'Resolve height value'.
10462 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10463 size = displaySize;
10466 // The purpose of clamping the viewport width to a minimum size is to
10467 // prevent page authors from setting it to a ridiculously small value.
10468 // If the page is actually being rendered in a very small area (as might
10469 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10470 // prevent the viewport from taking on that size.
10471 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10473 size.width = clamped(size.width, effectiveMinSize.width,
10474 float(kViewportMaxSize.width));
10476 // Also recalculate the default zoom, if it wasn't specified in the
10477 // metadata, and the width is specified.
10478 if (!mValidScaleFloat && !mWidthStrEmpty) {
10479 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10480 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10483 size.height = clamped(size.height, effectiveMinSize.height,
10484 float(kViewportMaxSize.height));
10486 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10487 // min and max, and then use the clamped value for the scale, the min, and
10488 // the max. If we don't have a positive scale, assert that we are setting
10489 // the auto scale flag.
10490 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10491 scaleFloat > CSSToScreenScale(0.0f)) {
10492 scaleFloat = scaleMinFloat = scaleMaxFloat =
10493 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10495 MOZ_ASSERT(
10496 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10497 "If we don't have a positive scale, we should be using auto scale.");
10499 // We need to perform a conversion, but only if the initial or maximum
10500 // scale were set explicitly by the user.
10501 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10502 scaleFloat <= scaleMaxFloat) {
10503 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10504 size.width = std::max(size.width, displaySize.width);
10505 size.height = std::max(size.height, displaySize.height);
10506 } else if (effectiveValidMaxScale) {
10507 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10508 size.width = std::max(size.width, displaySize.width);
10509 size.height = std::max(size.height, displaySize.height);
10512 return nsViewportInfo(
10513 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10514 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10515 : nsViewportInfo::AutoScaleFlag::AutoScale,
10516 effectiveZoomFlag, mViewportFit);
10520 ViewportMetaData Document::GetViewportMetaData() const {
10521 // The order of mMetaViewport is first-modified is first. We want the last
10522 // modified since Chrome uses the last one.
10523 // See https://webcompat.com/issues/20701#issuecomment-436054739
10524 return !mMetaViewports.IsEmpty() ? mMetaViewports.LastElement().mData
10525 : ViewportMetaData();
10528 void Document::AddMetaViewportElement(HTMLMetaElement* aElement,
10529 ViewportMetaData&& aData) {
10530 for (size_t i = 0; i < mMetaViewports.Length(); i++) {
10531 MetaViewportElementAndData& viewport = mMetaViewports[i];
10532 if (viewport.mElement == aElement) {
10533 if (viewport.mData == aData) {
10534 return;
10536 // Move the existing one to the tail since Chrome uses the last modified
10537 // viewport meta tag.
10538 mMetaViewports.RemoveElementAt(i);
10539 break;
10543 mMetaViewports.AppendElement(MetaViewportElementAndData{aElement, aData});
10544 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10545 mViewportType = Unknown;
10547 RefPtr<AsyncEventDispatcher> asyncDispatcher =
10548 new AsyncEventDispatcher(this, u"DOMMetaViewportFitChanged"_ns,
10549 CanBubble::eYes, ChromeOnlyDispatch::eYes);
10550 asyncDispatcher->RunDOMEventWhenSafe();
10553 void Document::RemoveMetaViewportElement(HTMLMetaElement* aElement) {
10554 for (MetaViewportElementAndData& viewport : mMetaViewports) {
10555 if (viewport.mElement == aElement) {
10556 mMetaViewports.RemoveElement(viewport);
10557 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10558 mViewportType = Unknown;
10560 RefPtr<AsyncEventDispatcher> asyncDispatcher =
10561 new AsyncEventDispatcher(this, u"DOMMetaViewportFitChanged"_ns,
10562 CanBubble::eYes, ChromeOnlyDispatch::eYes);
10563 asyncDispatcher->RunDOMEventWhenSafe();
10564 return;
10569 EventListenerManager* Document::GetOrCreateListenerManager() {
10570 if (!mListenerManager) {
10571 mListenerManager =
10572 new EventListenerManager(static_cast<EventTarget*>(this));
10573 SetFlags(NODE_HAS_LISTENERMANAGER);
10576 return mListenerManager;
10579 EventListenerManager* Document::GetExistingListenerManager() const {
10580 return mListenerManager;
10583 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10584 aVisitor.mCanHandle = true;
10585 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10586 // Bug 329119
10587 aVisitor.mForceContentDispatch = true;
10589 // Load events must not propagate to |window| object, see bug 335251.
10590 if (aVisitor.mEvent->mMessage != eLoad) {
10591 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10592 aVisitor.SetParentTarget(
10593 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10597 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10598 CallerType aCallerType,
10599 ErrorResult& rv) const {
10600 nsPresContext* presContext = GetPresContext();
10602 // Create event even without presContext.
10603 RefPtr<Event> ev =
10604 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10605 nullptr, aEventType, aCallerType);
10606 if (!ev) {
10607 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10608 return nullptr;
10610 WidgetEvent* e = ev->WidgetEventPtr();
10611 e->mFlags.mBubbles = false;
10612 e->mFlags.mCancelable = false;
10613 return ev.forget();
10616 void Document::FlushPendingNotifications(FlushType aType) {
10617 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10618 FlushPendingNotifications(flush);
10621 class nsDocumentOnStack {
10622 public:
10623 explicit nsDocumentOnStack(Document* aDoc) : mDoc(aDoc) {
10624 mDoc->IncreaseStackRefCnt();
10626 ~nsDocumentOnStack() { mDoc->DecreaseStackRefCnt(); }
10628 private:
10629 Document* mDoc;
10632 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10633 FlushType flushType = aFlush.mFlushType;
10635 nsDocumentOnStack dos(this);
10637 // We need to flush the sink for non-HTML documents (because the XML
10638 // parser still does insertion with deferred notifications). We
10639 // also need to flush the sink if this is a layout-related flush, to
10640 // make sure that layout is started as needed. But we can skip that
10641 // part if we have no presshell or if it's already done an initial
10642 // reflow.
10643 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10644 mPresShell && !mPresShell->DidInitialize())) &&
10645 (mParser || mWeakSink)) {
10646 nsCOMPtr<nsIContentSink> sink;
10647 if (mParser) {
10648 sink = mParser->GetContentSink();
10649 } else {
10650 sink = do_QueryReferent(mWeakSink);
10651 if (!sink) {
10652 mWeakSink = nullptr;
10655 // Determine if it is safe to flush the sink notifications
10656 // by determining if it safe to flush all the presshells.
10657 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10658 sink->FlushPendingNotifications(flushType);
10662 // Should we be flushing pending binding constructors in here?
10664 if (flushType <= FlushType::ContentAndNotify) {
10665 // Nothing to do here
10666 return;
10669 // If we have a parent we must flush the parent too to ensure that our
10670 // container is reflowed if its size was changed.
10672 // We do it only if the subdocument and the parent can observe each other
10673 // synchronously (that is, if we're not cross-origin), to avoid work that is
10674 // not observable, and if the parent document has finished loading all its
10675 // render-blocking stylesheets and may start laying out the document, to avoid
10676 // unnecessary flashes of unstyled content on the parent document. Note that
10677 // this last bit means that size-dependent media queries in this document may
10678 // produce incorrect results temporarily.
10680 // But if it's not safe to flush ourselves, then don't flush the parent, since
10681 // that can cause things like resizes of our frame's widget, which we can't
10682 // handle while flushing is unsafe.
10683 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10684 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10685 ChangesToFlush parentFlush = aFlush;
10686 if (flushType >= FlushType::Style) {
10687 // Since media queries mean that a size change of our container can affect
10688 // style, we need to promote a style flush on ourself to a layout flush on
10689 // our parent, since we need our container to be the correct size to
10690 // determine the correct style.
10691 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10693 mParentDocument->FlushPendingNotifications(parentFlush);
10696 if (RefPtr<PresShell> presShell = GetPresShell()) {
10697 presShell->FlushPendingNotifications(aFlush);
10701 void Document::FlushExternalResources(FlushType aType) {
10702 NS_ASSERTION(
10703 aType >= FlushType::Style,
10704 "should only need to flush for style or higher in external resources");
10705 if (GetDisplayDocument()) {
10706 return;
10709 auto flush = [aType](Document& aDoc) {
10710 aDoc.FlushPendingNotifications(aType);
10711 return CallState::Continue;
10714 EnumerateExternalResources(flush);
10717 void Document::SetXMLDeclaration(const char16_t* aVersion,
10718 const char16_t* aEncoding,
10719 const int32_t aStandalone) {
10720 if (!aVersion || *aVersion == '\0') {
10721 mXMLDeclarationBits = 0;
10722 return;
10725 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10727 if (aEncoding && *aEncoding != '\0') {
10728 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10731 if (aStandalone == 1) {
10732 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10733 XML_DECLARATION_BITS_STANDALONE_YES;
10734 } else if (aStandalone == 0) {
10735 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10739 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10740 nsAString& aStandalone) {
10741 aVersion.Truncate();
10742 aEncoding.Truncate();
10743 aStandalone.Truncate();
10745 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10746 return;
10749 // always until we start supporting 1.1 etc.
10750 aVersion.AssignLiteral("1.0");
10752 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
10753 // This is what we have stored, not necessarily what was written
10754 // in the original
10755 GetCharacterSet(aEncoding);
10758 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
10759 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
10760 aStandalone.AssignLiteral("yes");
10761 } else {
10762 aStandalone.AssignLiteral("no");
10767 bool Document::IsScriptEnabled() {
10768 // If this document is sandboxed without 'allow-scripts'
10769 // script is not enabled
10770 if (HasScriptsBlockedBySandbox()) {
10771 return false;
10774 nsCOMPtr<nsIScriptGlobalObject> globalObject =
10775 do_QueryInterface(GetInnerWindow());
10776 if (!globalObject || !globalObject->HasJSGlobal()) {
10777 return false;
10780 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
10781 .Allowed();
10784 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
10785 PRTime modDate = 0;
10786 nsresult rv;
10788 nsCOMPtr<nsIHttpChannel> httpChannel;
10789 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
10790 if (NS_WARN_IF(NS_FAILED(rv))) {
10791 return;
10794 if (httpChannel) {
10795 nsAutoCString tmp;
10796 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
10798 if (NS_SUCCEEDED(rv)) {
10799 PRTime time;
10800 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
10801 if (st == PR_SUCCESS) {
10802 modDate = time;
10806 static const char* const headers[] = {
10807 "default-style", "content-style-type", "content-language",
10808 "content-disposition", "refresh", "x-dns-prefetch-control",
10809 "x-frame-options",
10810 // add more http headers if you need
10811 // XXXbz don't add content-location support without reading bug
10812 // 238654 and its dependencies/dups first.
10815 nsAutoCString headerVal;
10816 const char* const* name = headers;
10817 while (*name) {
10818 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
10819 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
10820 RefPtr<nsAtom> key = NS_Atomize(*name);
10821 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
10823 ++name;
10825 } else {
10826 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
10827 if (fileChannel) {
10828 nsCOMPtr<nsIFile> file;
10829 fileChannel->GetFile(getter_AddRefs(file));
10830 if (file) {
10831 PRTime msecs;
10832 rv = file->GetLastModifiedTime(&msecs);
10834 if (NS_SUCCEEDED(rv)) {
10835 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
10838 } else {
10839 nsAutoCString contentDisp;
10840 rv = aChannel->GetContentDispositionHeader(contentDisp);
10841 if (NS_SUCCEEDED(rv)) {
10842 SetHeaderData(nsGkAtoms::headerContentDisposition,
10843 NS_ConvertASCIItoUTF16(contentDisp));
10848 mLastModified.Truncate();
10849 if (modDate != 0) {
10850 GetFormattedTimeString(modDate, mLastModified);
10854 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
10855 // set any HTTP-EQUIV data into document's header data as well as url
10856 nsAutoString header;
10857 aMetaElement->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header);
10858 if (!header.IsEmpty()) {
10859 // Ignore META REFRESH when document is sandboxed from automatic features.
10860 nsContentUtils::ASCIIToLower(header);
10861 if (nsGkAtoms::refresh->Equals(header) &&
10862 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
10863 return;
10866 nsAutoString result;
10867 aMetaElement->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result);
10868 if (!result.IsEmpty()) {
10869 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
10870 SetHeaderData(fieldAtom, result);
10874 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
10875 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
10876 nsAutoString result;
10877 aMetaElement->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result);
10878 if (!result.IsEmpty()) {
10879 nsContentUtils::ASCIIToLower(result);
10880 SetHeaderData(nsGkAtoms::handheldFriendly, result);
10885 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
10886 nsAtom* aPrefix,
10887 int32_t aNamespaceID,
10888 const nsAString* aIs) {
10889 #ifdef DEBUG
10890 nsAutoString qName;
10891 if (aPrefix) {
10892 aPrefix->ToString(qName);
10893 qName.Append(':');
10895 qName.Append(aName);
10897 // Note: "a:b:c" is a valid name in non-namespaces XML, and
10898 // Document::CreateElement can call us with such a name and no prefix,
10899 // which would cause an error if we just used true here.
10900 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
10901 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
10902 "Don't pass invalid prefixes to Document::CreateElem, "
10903 "check caller.");
10904 #endif
10906 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
10907 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
10908 getter_AddRefs(nodeInfo));
10909 NS_ENSURE_TRUE(nodeInfo, nullptr);
10911 nsCOMPtr<Element> element;
10912 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
10913 NOT_FROM_PARSER, aIs);
10914 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
10917 bool Document::IsSafeToFlush() const {
10918 PresShell* presShell = GetPresShell();
10919 if (!presShell) {
10920 return true;
10922 return presShell->IsSafeToFlush();
10925 void Document::Sanitize() {
10926 // Sanitize the document by resetting all (current and former) password fields
10927 // and any form fields with autocomplete=off to their default values. We do
10928 // this now, instead of when the presentation is restored, to offer some
10929 // protection in case there is ever an exploit that allows a cached document
10930 // to be accessed from a different document.
10932 // First locate all input elements, regardless of whether they are
10933 // in a form, and reset the password and autocomplete=off elements.
10935 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
10937 nsAutoString value;
10939 uint32_t length = nodes->Length(true);
10940 for (uint32_t i = 0; i < length; ++i) {
10941 NS_ASSERTION(nodes->Item(i), "null item in node list!");
10943 RefPtr<HTMLInputElement> input =
10944 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
10945 if (!input) continue;
10947 input->GetAttr(nsGkAtoms::autocomplete, value);
10948 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
10949 input->Reset();
10953 // Now locate all _form_ elements that have autocomplete=off and reset them
10954 nodes = GetElementsByTagName(u"form"_ns);
10956 length = nodes->Length(true);
10957 for (uint32_t i = 0; i < length; ++i) {
10958 // Reset() may change the list dynamically.
10959 RefPtr<HTMLFormElement> form =
10960 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
10961 if (!form) continue;
10963 form->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, value);
10964 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
10968 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
10969 if (!mSubDocuments) {
10970 return;
10973 // PLDHashTable::Iterator can't handle modifications while iterating so we
10974 // copy all entries to an array first before calling any callbacks.
10975 AutoTArray<RefPtr<Document>, 8> subdocs;
10976 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
10977 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
10978 if (Document* subdoc = entry->mSubDocument) {
10979 subdocs.AppendElement(subdoc);
10982 for (auto& subdoc : subdocs) {
10983 if (aCallback(*subdoc) == CallState::Stop) {
10984 break;
10989 void Document::CollectDescendantDocuments(
10990 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
10991 if (!mSubDocuments) {
10992 return;
10995 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
10996 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
10997 const Document* subdoc = entry->mSubDocument;
10998 if (subdoc) {
10999 if (aCallback(subdoc)) {
11000 aDescendants.AppendElement(entry->mSubDocument);
11002 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11007 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11008 uint16_t& aBFCacheCombo,
11009 bool aIncludeSubdocuments,
11010 bool aAllowUnloadListeners) {
11011 bool ret = true;
11013 if (!IsBFCachingAllowed()) {
11014 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11015 ret = false;
11018 nsAutoCString uri;
11019 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11020 if (mDocumentURI) {
11021 mDocumentURI->GetSpec(uri);
11025 if (EventHandlingSuppressed()) {
11026 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11027 ("Save of %s blocked on event handling suppression", uri.get()));
11028 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11029 ret = false;
11032 // Do not allow suspended windows to be placed in the
11033 // bfcache. This method is also used to verify a document
11034 // coming out of the bfcache is ok to restore, though. So
11035 // we only want to block suspend windows that aren't also
11036 // frozen.
11037 nsPIDOMWindowInner* win = GetInnerWindow();
11038 if (win && win->IsSuspended() && !win->IsFrozen()) {
11039 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11040 ("Save of %s blocked on suspended Window", uri.get()));
11041 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11042 ret = false;
11045 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11046 bool thirdParty = false;
11047 // Currently some other mobile browsers seem to bfcache only cross-domain
11048 // pages, but bfcache those also when there are unload event listeners, so
11049 // this is trying to match that behavior as much as possible.
11050 bool allowUnloadListeners =
11051 aAllowUnloadListeners &&
11052 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11053 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11054 channel, &thirdParty)) &&
11055 thirdParty));
11057 // Check our event listener manager for unload/beforeunload listeners.
11058 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11059 if (!allowUnloadListeners && piTarget) {
11060 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11061 if (manager && manager->HasUnloadListeners()) {
11062 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11063 ("Save of %s blocked due to unload handlers", uri.get()));
11064 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11065 ret = false;
11069 // Check if we have pending network requests
11070 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11071 if (loadGroup) {
11072 nsCOMPtr<nsISimpleEnumerator> requests;
11073 loadGroup->GetRequests(getter_AddRefs(requests));
11075 bool hasMore = false;
11077 // We want to bail out if we have any requests other than aNewRequest (or
11078 // in the case when aNewRequest is a part of a multipart response the base
11079 // channel the multipart response is coming in on).
11080 nsCOMPtr<nsIChannel> baseChannel;
11081 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11082 if (part) {
11083 part->GetBaseChannel(getter_AddRefs(baseChannel));
11086 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11087 nsCOMPtr<nsISupports> elem;
11088 requests->GetNext(getter_AddRefs(elem));
11090 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11091 if (request && request != aNewRequest && request != baseChannel) {
11092 // Favicon loads don't need to block caching.
11093 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11094 if (channel) {
11095 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11096 if (li->InternalContentPolicyType() ==
11097 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11098 continue;
11102 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11103 nsAutoCString requestName;
11104 request->GetName(requestName);
11105 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11106 ("Save of %s blocked because document has request %s",
11107 uri.get(), requestName.get()));
11109 aBFCacheCombo |= BFCacheStatus::REQUEST;
11110 ret = false;
11115 // Check if we have active GetUserMedia use
11116 if (MediaManager::Exists() && win &&
11117 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11118 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11119 ("Save of %s blocked due to GetUserMedia", uri.get()));
11120 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11121 ret = false;
11124 #ifdef MOZ_WEBRTC
11125 // Check if we have active PeerConnections
11126 if (win && win->HasActivePeerConnections()) {
11127 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11128 ("Save of %s blocked due to PeerConnection", uri.get()));
11129 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11130 ret = false;
11132 #endif // MOZ_WEBRTC
11134 // Don't save presentations for documents containing EME content, so that
11135 // CDMs reliably shutdown upon user navigation.
11136 if (ContainsEMEContent()) {
11137 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11138 ret = false;
11141 // Don't save presentations for documents containing MSE content, to
11142 // reduce memory usage.
11143 if (ContainsMSEContent()) {
11144 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11145 ("Save of %s blocked due to MSE use", uri.get()));
11146 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11147 ret = false;
11150 if (aIncludeSubdocuments && mSubDocuments) {
11151 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11152 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11153 Document* subdoc = entry->mSubDocument;
11155 uint16_t subDocBFCacheCombo = 0;
11156 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11157 bool canCache =
11158 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11159 true, allowUnloadListeners)
11160 : false;
11161 if (!canCache) {
11162 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11163 ("Save of %s blocked due to subdocument blocked", uri.get()));
11164 aBFCacheCombo |= subDocBFCacheCombo;
11165 ret = false;
11170 if (!mozilla::BFCacheInParent()) {
11171 // BFCache is currently not compatible with remote subframes (bug 1609324)
11172 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11173 for (auto& child : browsingContext->Children()) {
11174 if (!child->IsInProcess()) {
11175 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11176 ret = false;
11177 break;
11183 if (win) {
11184 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11185 #ifdef MOZ_WEBSPEECH
11186 if (globalWindow->HasActiveSpeechSynthesis()) {
11187 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11188 ("Save of %s blocked due to Speech use", uri.get()));
11189 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11190 ret = false;
11192 #endif
11193 if (globalWindow->HasUsedVR()) {
11194 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11195 ("Save of %s blocked due to having used VR", uri.get()));
11196 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11197 ret = false;
11201 return ret;
11204 void Document::Destroy() {
11205 // The ContentViewer wants to release the document now. So, tell our content
11206 // to drop any references to the document so that it can be destroyed.
11207 if (mIsGoingAway) {
11208 return;
11211 ReportDocumentUseCounters();
11212 SetDevToolsWatchingDOMMutations(false);
11214 mIsGoingAway = true;
11216 ScriptLoader()->Destroy();
11217 SetScriptGlobalObject(nullptr);
11218 RemovedFromDocShell();
11220 bool oldVal = mInUnlinkOrDeletion;
11221 mInUnlinkOrDeletion = true;
11223 #ifdef DEBUG
11224 uint32_t oldChildCount = GetChildCount();
11225 #endif
11227 for (nsIContent* child = GetFirstChild(); child;
11228 child = child->GetNextSibling()) {
11229 child->DestroyContent();
11230 MOZ_ASSERT(child->GetParentNode() == this);
11232 MOZ_ASSERT(oldChildCount == GetChildCount());
11233 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11235 mInUnlinkOrDeletion = oldVal;
11237 mLayoutHistoryState = nullptr;
11239 if (mOriginalDocument) {
11240 mOriginalDocument->mLatestStaticClone = nullptr;
11243 if (IsStaticDocument()) {
11244 RemoveProperty(nsGkAtoms::printselectionranges);
11247 // Shut down our external resource map. We might not need this for
11248 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11249 // tearing down all those frame trees right now is the right thing to do.
11250 mExternalResourceMap.Shutdown();
11252 // Manually break cycles via promise's global object pointer.
11253 mReadyForIdle = nullptr;
11254 mOrientationPendingPromise = nullptr;
11256 // To break cycles.
11257 mPreloadService.ClearAllPreloads();
11259 if (mDocumentL10n) {
11260 mDocumentL10n->Destroy();
11264 void Document::RemovedFromDocShell() {
11265 mEditingState = EditingState::eOff;
11267 if (mRemovedFromDocShell) return;
11269 mRemovedFromDocShell = true;
11270 EnumerateActivityObservers(NotifyActivityChanged);
11272 for (nsIContent* child = GetFirstChild(); child;
11273 child = child->GetNextSibling()) {
11274 child->SaveSubtreeState();
11277 nsIDocShell* docShell = GetDocShell();
11278 if (docShell) {
11279 docShell->SynchronizeLayoutHistoryState();
11283 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
11284 const {
11285 nsCOMPtr<nsILayoutHistoryState> state;
11286 if (!mScriptGlobalObject) {
11287 state = mLayoutHistoryState;
11288 } else {
11289 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
11290 if (docShell) {
11291 docShell->GetLayoutHistoryState(getter_AddRefs(state));
11295 return state.forget();
11298 void Document::EnsureOnloadBlocker() {
11299 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11300 // -- it's not ours.
11301 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
11302 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11303 if (loadGroup) {
11304 // Check first to see if mOnloadBlocker is in the loadgroup.
11305 nsCOMPtr<nsISimpleEnumerator> requests;
11306 loadGroup->GetRequests(getter_AddRefs(requests));
11308 bool hasMore = false;
11309 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11310 nsCOMPtr<nsISupports> elem;
11311 requests->GetNext(getter_AddRefs(elem));
11312 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11313 if (request && request == mOnloadBlocker) {
11314 return;
11318 // Not in the loadgroup, so add it.
11319 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11324 void Document::AsyncBlockOnload() {
11325 while (mAsyncOnloadBlockCount) {
11326 --mAsyncOnloadBlockCount;
11327 BlockOnload();
11331 void Document::BlockOnload() {
11332 if (mDisplayDocument) {
11333 mDisplayDocument->BlockOnload();
11334 return;
11337 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11338 // -- it's not ours.
11339 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
11340 if (!nsContentUtils::IsSafeToRunScript()) {
11341 // Because AddRequest may lead to OnStateChange calls in chrome,
11342 // block onload only when there are no script blockers.
11343 ++mAsyncOnloadBlockCount;
11344 if (mAsyncOnloadBlockCount == 1) {
11345 nsContentUtils::AddScriptRunner(NewRunnableMethod(
11346 "Document::AsyncBlockOnload", this, &Document::AsyncBlockOnload));
11348 return;
11350 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11351 if (loadGroup) {
11352 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11355 ++mOnloadBlockCount;
11358 void Document::UnblockOnload(bool aFireSync) {
11359 if (mDisplayDocument) {
11360 mDisplayDocument->UnblockOnload(aFireSync);
11361 return;
11364 if (mOnloadBlockCount == 0 && mAsyncOnloadBlockCount == 0) {
11365 MOZ_ASSERT_UNREACHABLE(
11366 "More UnblockOnload() calls than BlockOnload() "
11367 "calls; dropping call");
11368 return;
11371 --mOnloadBlockCount;
11373 if (mOnloadBlockCount == 0) {
11374 if (mScriptGlobalObject) {
11375 // Only manipulate the loadgroup in this case, because if
11376 // mScriptGlobalObject is null, it's not ours.
11377 if (aFireSync && mAsyncOnloadBlockCount == 0) {
11378 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
11379 ++mOnloadBlockCount;
11380 DoUnblockOnload();
11381 } else {
11382 PostUnblockOnloadEvent();
11384 } else if (mIsBeingUsedAsImage) {
11385 // To correctly unblock onload for a document that contains an SVG
11386 // image, we need to know when all of the SVG document's resources are
11387 // done loading, in a way comparable to |window.onload|. We fire this
11388 // event to indicate that the SVG should be considered fully loaded.
11389 // Because scripting is disabled on SVG-as-image documents, this event
11390 // is not accessible to content authors. (See bug 837315.)
11391 RefPtr<AsyncEventDispatcher> asyncDispatcher =
11392 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
11393 CanBubble::eNo, ChromeOnlyDispatch::eNo);
11394 asyncDispatcher->PostDOMEvent();
11399 class nsUnblockOnloadEvent : public Runnable {
11400 public:
11401 explicit nsUnblockOnloadEvent(Document* aDoc)
11402 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
11403 NS_IMETHOD Run() override {
11404 mDoc->DoUnblockOnload();
11405 return NS_OK;
11408 private:
11409 RefPtr<Document> mDoc;
11412 void Document::PostUnblockOnloadEvent() {
11413 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11414 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
11415 nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
11416 if (NS_SUCCEEDED(rv)) {
11417 // Stabilize block count so we don't post more events while this one is up
11418 ++mOnloadBlockCount;
11419 } else {
11420 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
11424 void Document::DoUnblockOnload() {
11425 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
11426 MOZ_ASSERT(mOnloadBlockCount != 0,
11427 "Shouldn't have a count of zero here, since we stabilized in "
11428 "PostUnblockOnloadEvent");
11430 --mOnloadBlockCount;
11432 if (mOnloadBlockCount != 0) {
11433 // We blocked again after the last unblock. Nothing to do here. We'll
11434 // post a new event when we unblock again.
11435 return;
11438 if (mAsyncOnloadBlockCount != 0) {
11439 // We need to wait until the async onload block has been handled.
11441 // FIXME(emilio): Shouldn't we return here??
11442 PostUnblockOnloadEvent();
11445 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11446 // -- it's not ours.
11447 if (mScriptGlobalObject) {
11448 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11449 if (loadGroup) {
11450 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
11455 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
11456 for (nsIFrame* f = aFrame; f;
11457 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
11458 nsIContent* content = f->GetContent();
11459 if (!content || content->IsInNativeAnonymousSubtree()) continue;
11461 if (content->OwnerDoc() == this) {
11462 return content;
11464 // We must be in a subdocument so jump directly to the root frame.
11465 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
11466 // the containing document.
11467 f = f->PresContext()->GetPresShell()->GetRootFrame();
11470 return nullptr;
11473 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
11474 const nsAString& aType, bool aInFrameSwap,
11475 bool aPersisted, bool aOnlySystemGroup) {
11476 if (!aDispatchTarget) {
11477 return;
11480 PageTransitionEventInit init;
11481 init.mBubbles = true;
11482 init.mCancelable = true;
11483 init.mPersisted = aPersisted;
11484 init.mInFrameSwap = aInFrameSwap;
11486 RefPtr<PageTransitionEvent> event =
11487 PageTransitionEvent::Constructor(this, aType, init);
11489 event->SetTrusted(true);
11490 event->SetTarget(this);
11491 if (aOnlySystemGroup) {
11492 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
11494 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
11495 nullptr);
11498 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
11499 bool aOnlySystemGroup) {
11500 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11501 nsCString uri;
11502 if (GetDocumentURI()) {
11503 uri = GetDocumentURI()->GetSpecOrDefault();
11505 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11506 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
11509 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11510 MOZ_DIAGNOSTIC_ASSERT(
11511 inFrameLoaderSwap ==
11512 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11514 Element* root = GetRootElement();
11515 if (aPersisted && root) {
11516 // Send out notifications that our <link> elements are attached.
11517 RefPtr<nsContentList> links =
11518 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11520 uint32_t linkCount = links->Length(true);
11521 for (uint32_t i = 0; i < linkCount; ++i) {
11522 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
11526 // See Document
11527 if (!inFrameLoaderSwap) {
11528 if (aPersisted) {
11529 ImageTracker()->SetAnimatingState(true);
11532 // Set mIsShowing before firing events, in case those event handlers
11533 // move us around.
11534 mIsShowing = true;
11535 mVisible = true;
11537 UpdateVisibilityState();
11540 EnumerateActivityObservers(NotifyActivityChanged);
11542 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11543 aExternalResource.OnPageShow(aPersisted, nullptr);
11544 return CallState::Continue;
11546 EnumerateExternalResources(notifyExternal);
11548 if (mAnimationController) {
11549 mAnimationController->OnPageShow();
11552 if (!mIsBeingUsedAsImage) {
11553 // Dispatch observer notification to notify observers page is shown.
11554 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11555 if (os) {
11556 nsIPrincipal* principal = NodePrincipal();
11557 os->NotifyObservers(ToSupports(this),
11558 principal->IsSystemPrincipal() ? "chrome-page-shown"
11559 : "content-page-shown",
11560 nullptr);
11563 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11564 if (!target) {
11565 target = do_QueryInterface(GetWindow());
11567 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11568 aPersisted, aOnlySystemGroup);
11572 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11573 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11574 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11575 FullscreenEventType::Change, &aDocument, aTarget);
11576 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11577 std::move(pendingEvent));
11581 static void ClearPendingFullscreenRequests(Document* aDoc);
11583 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11584 bool aOnlySystemGroup) {
11585 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11586 nsCString uri;
11587 if (GetDocumentURI()) {
11588 uri = GetDocumentURI()->GetSpecOrDefault();
11590 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11591 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11594 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11595 MOZ_DIAGNOSTIC_ASSERT(
11596 inFrameLoaderSwap ==
11597 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11599 // Send out notifications that our <link> elements are detached,
11600 // but only if this is not a full unload.
11601 Element* root = GetRootElement();
11602 if (aPersisted && root) {
11603 RefPtr<nsContentList> links =
11604 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11606 uint32_t linkCount = links->Length(true);
11607 for (uint32_t i = 0; i < linkCount; ++i) {
11608 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
11612 if (mAnimationController) {
11613 mAnimationController->OnPageHide();
11616 if (!inFrameLoaderSwap) {
11617 if (aPersisted) {
11618 // We do not stop the animations (bug 1024343) when the page is refreshing
11619 // while being dragged out.
11620 ImageTracker()->SetAnimatingState(false);
11623 // Set mIsShowing before firing events, in case those event handlers
11624 // move us around.
11625 mIsShowing = false;
11626 mVisible = false;
11629 ExitPointerLock();
11631 if (!mIsBeingUsedAsImage) {
11632 // Dispatch observer notification to notify observers page is hidden.
11633 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11634 if (os) {
11635 nsIPrincipal* principal = NodePrincipal();
11636 os->NotifyObservers(ToSupports(this),
11637 principal->IsSystemPrincipal()
11638 ? "chrome-page-hidden"
11639 : "content-page-hidden",
11640 nullptr);
11643 // Now send out a PageHide event.
11644 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11645 if (!target) {
11646 target = do_QueryInterface(GetWindow());
11649 PageUnloadingEventTimeStamp timeStamp(this);
11650 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11651 aPersisted, aOnlySystemGroup);
11655 if (!inFrameLoaderSwap) {
11656 UpdateVisibilityState();
11659 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11660 aExternalResource.OnPageHide(aPersisted, nullptr);
11661 return CallState::Continue;
11663 EnumerateExternalResources(notifyExternal);
11664 EnumerateActivityObservers(NotifyActivityChanged);
11666 ClearPendingFullscreenRequests(this);
11667 if (GetUnretargetedFullScreenElement()) {
11668 // If this document was fullscreen, we should exit fullscreen in this
11669 // doctree branch. This ensures that if the user navigates while in
11670 // fullscreen mode we don't leave its still visible ancestor documents
11671 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11672 // root document, as this will exit fullscreen in all the root's
11673 // descendant documents. Note that documents are removed from the
11674 // doctree by the time OnPageHide() is called, so we must store a
11675 // reference to the root (in Document::mFullscreenRoot) since we can't
11676 // just traverse the doctree to get the root.
11677 Document::ExitFullscreenInDocTree(this);
11679 // Since the document is removed from the doctree before OnPageHide() is
11680 // called, ExitFullscreen() can't traverse from the root down to *this*
11681 // document, so we must manually call CleanupFullscreenState() below too.
11682 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11683 // so we *must* call it after ExitFullscreen(), not before.
11684 // OnPageHide() is called in every hidden (i.e. descendant) document,
11685 // so calling CleanupFullscreenState() here will ensure all hidden
11686 // documents have their fullscreen state reset.
11687 CleanupFullscreenState();
11689 // The fullscreenchange event is to be queued in the refresh driver,
11690 // however a hidden page wouldn't trigger that again, so it makes no
11691 // sense to dispatch such event here.
11695 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11696 NS_ASSERTION(
11697 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11698 "mSubtreeModifiedTargets not cleared after dispatching?");
11699 ++mSubtreeModifiedDepth;
11700 if (aTarget) {
11701 // MayDispatchMutationEvent is often called just before this method,
11702 // so it has already appended the node to mSubtreeModifiedTargets.
11703 int32_t count = mSubtreeModifiedTargets.Count();
11704 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11705 mSubtreeModifiedTargets.AppendObject(aTarget);
11710 void Document::MutationEventDispatched(nsINode* aTarget) {
11711 --mSubtreeModifiedDepth;
11712 if (mSubtreeModifiedDepth == 0) {
11713 int32_t count = mSubtreeModifiedTargets.Count();
11714 if (!count) {
11715 return;
11718 nsPIDOMWindowInner* window = GetInnerWindow();
11719 if (window &&
11720 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11721 mSubtreeModifiedTargets.Clear();
11722 return;
11725 nsCOMArray<nsINode> realTargets;
11726 for (int32_t i = 0; i < count; ++i) {
11727 nsINode* possibleTarget = mSubtreeModifiedTargets[i];
11728 nsCOMPtr<nsIContent> content = do_QueryInterface(possibleTarget);
11729 if (content && content->ChromeOnlyAccess()) {
11730 continue;
11733 nsINode* commonAncestor = nullptr;
11734 int32_t realTargetCount = realTargets.Count();
11735 for (int32_t j = 0; j < realTargetCount; ++j) {
11736 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
11737 possibleTarget, realTargets[j]);
11738 if (commonAncestor) {
11739 realTargets.ReplaceObjectAt(commonAncestor, j);
11740 break;
11743 if (!commonAncestor) {
11744 realTargets.AppendObject(possibleTarget);
11748 mSubtreeModifiedTargets.Clear();
11750 int32_t realTargetCount = realTargets.Count();
11751 for (int32_t k = 0; k < realTargetCount; ++k) {
11752 InternalMutationEvent mutation(true, eLegacySubtreeModified);
11753 (new AsyncEventDispatcher(realTargets[k], mutation))
11754 ->RunDOMEventWhenSafe();
11759 void Document::DestroyElementMaps() {
11760 #ifdef DEBUG
11761 mStyledLinksCleared = true;
11762 #endif
11763 mStyledLinks.Clear();
11764 // Notify ID change listeners before clearing the identifier map.
11765 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
11766 iter.Get()->ClearAndNotify();
11768 mIdentifierMap.Clear();
11769 mComposedShadowRoots.Clear();
11770 mResponsiveContent.Clear();
11771 IncrementExpandoGeneration(*this);
11774 void Document::RefreshLinkHrefs() {
11775 // Get a list of all links we know about. We will reset them, which will
11776 // remove them from the document, so we need a copy of what is in the
11777 // hashtable.
11778 const LinkArray linksToNotify = ToArray(mStyledLinks);
11780 // Reset all of our styled links.
11781 nsAutoScriptBlocker scriptBlocker;
11782 for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
11783 linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref());
11787 nsresult Document::CloneDocHelper(Document* clone) const {
11788 clone->mIsStaticDocument = mCreatingStaticClone;
11790 // Init document
11791 nsresult rv = clone->Init();
11792 NS_ENSURE_SUCCESS(rv, rv);
11794 if (mCreatingStaticClone) {
11795 if (mOriginalDocument) {
11796 clone->mOriginalDocument = mOriginalDocument;
11797 } else {
11798 clone->mOriginalDocument = const_cast<Document*>(this);
11800 clone->mOriginalDocument->mLatestStaticClone = clone;
11801 clone->mOriginalDocument->mStaticCloneCount++;
11803 nsCOMPtr<nsILoadGroup> loadGroup;
11805 // |mDocumentContainer| is the container of the document that is being
11806 // created and not the original container. See CreateStaticClone function().
11807 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
11808 if (docLoader) {
11809 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
11811 nsCOMPtr<nsIChannel> channel = GetChannel();
11812 nsCOMPtr<nsIURI> uri;
11813 if (channel) {
11814 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
11815 } else {
11816 uri = Document::GetDocumentURI();
11818 clone->mChannel = channel;
11819 if (uri) {
11820 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
11823 clone->mIsSrcdocDocument = mIsSrcdocDocument;
11825 clone->SetContainer(mDocumentContainer);
11827 // Setup the navigation time. This will be needed by any animations in the
11828 // document, even if they are only paused.
11829 MOZ_ASSERT(!clone->GetNavigationTiming(),
11830 "Navigation time was already set?");
11831 if (mTiming) {
11832 RefPtr<nsDOMNavigationTiming> timing =
11833 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
11834 clone->SetNavigationTiming(timing);
11836 clone->SetCsp(mCSP);
11839 // Now ensure that our clone has the same URI, base URI, and principal as us.
11840 // We do this after the mCreatingStaticClone block above, because that block
11841 // can set the base URI to an incorrect value in cases when base URI
11842 // information came from the channel. So we override explicitly, and do it
11843 // for all these properties, in case ResetToURI messes with any of the rest of
11844 // them.
11845 clone->SetDocumentURI(Document::GetDocumentURI());
11846 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
11847 clone->SetPrincipals(NodePrincipal(), mPartitionedPrincipal);
11848 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
11849 clone->mDocumentBaseURI = mDocumentBaseURI;
11850 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
11851 clone->mReferrerInfo =
11852 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
11853 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
11855 bool hasHadScriptObject = true;
11856 nsIScriptGlobalObject* scriptObject =
11857 GetScriptHandlingObject(hasHadScriptObject);
11858 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
11859 if (mCreatingStaticClone) {
11860 // If we're doing a static clone (print, print preview), then we're going to
11861 // be setting a scope object after the clone. It's better to set it only
11862 // once, so we don't do that here. However, we do want to act as if there is
11863 // a script handling object. So we set mHasHadScriptHandlingObject.
11864 clone->mHasHadScriptHandlingObject = true;
11865 } else if (scriptObject) {
11866 clone->SetScriptHandlingObject(scriptObject);
11867 } else {
11868 clone->SetScopeObject(GetScopeObject());
11870 // Make the clone a data document
11871 clone->SetLoadedAsData(
11872 true,
11873 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
11875 // Misc state
11877 // State from Document
11878 clone->mCharacterSet = mCharacterSet;
11879 clone->mCharacterSetSource = mCharacterSetSource;
11880 clone->SetCompatibilityMode(mCompatMode);
11881 clone->mBidiOptions = mBidiOptions;
11882 clone->mContentLanguage = mContentLanguage;
11883 clone->SetContentTypeInternal(GetContentTypeInternal());
11884 clone->mSecurityInfo = mSecurityInfo;
11886 // State from Document
11887 clone->mType = mType;
11888 clone->mXMLDeclarationBits = mXMLDeclarationBits;
11889 clone->mBaseTarget = mBaseTarget;
11891 return NS_OK;
11894 void Document::NotifyLoading(bool aNewParentIsLoading,
11895 const ReadyState& aCurrentState,
11896 ReadyState aNewState) {
11897 // Mirror the top-level loading state down to all subdocuments
11898 bool was_loading = mAncestorIsLoading ||
11899 aCurrentState == READYSTATE_LOADING ||
11900 aCurrentState == READYSTATE_INTERACTIVE;
11901 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
11902 aNewState == READYSTATE_INTERACTIVE; // new value for state
11903 bool set_load_state = was_loading != is_loading;
11905 MOZ_LOG(
11906 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
11907 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
11908 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
11909 "set_load_state: %d",
11910 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
11911 (int)aNewState, was_loading, is_loading, set_load_state));
11913 mAncestorIsLoading = aNewParentIsLoading;
11914 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
11915 // Tell our innerwindow (and thus TimeoutManager)
11916 nsPIDOMWindowInner* inner = GetInnerWindow();
11917 if (inner) {
11918 inner->SetActiveLoadingState(is_loading);
11920 BrowsingContext* context = GetBrowsingContext();
11921 if (context) {
11922 // Don't use PreOrderWalk to mirror this down; go down one level as a
11923 // time so we can set mAncestorIsLoading and take into account the
11924 // readystates of the subdocument. In the child process it will call
11925 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
11926 // iterate it's children
11927 for (auto& child : context->Children()) {
11928 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
11929 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
11930 // Setting ancestor loading on a discarded browsing context has no
11931 // effect.
11932 Unused << child->SetAncestorLoading(is_loading);
11938 void Document::SetReadyStateInternal(ReadyState aReadyState,
11939 bool aUpdateTimingInformation) {
11940 if (aReadyState == READYSTATE_UNINITIALIZED) {
11941 // Transition back to uninitialized happens only to keep assertions happy
11942 // right before readyState transitions to something else. Make this
11943 // transition undetectable by Web content.
11944 mReadyState = aReadyState;
11945 return;
11948 if (IsTopLevelContentDocument()) {
11949 if (aReadyState == READYSTATE_LOADING) {
11950 AddToplevelLoadingDocument(this);
11951 } else if (aReadyState == READYSTATE_COMPLETE) {
11952 RemoveToplevelLoadingDocument(this);
11956 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
11957 mLoadingTimeStamp = TimeStamp::Now();
11959 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
11960 mReadyState = aReadyState;
11961 if (aUpdateTimingInformation && mTiming) {
11962 switch (aReadyState) {
11963 case READYSTATE_LOADING:
11964 mTiming->NotifyDOMLoading(GetDocumentURI());
11965 break;
11966 case READYSTATE_INTERACTIVE:
11967 mTiming->NotifyDOMInteractive(GetDocumentURI());
11968 break;
11969 case READYSTATE_COMPLETE:
11970 mTiming->NotifyDOMComplete(GetDocumentURI());
11971 break;
11972 default:
11973 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
11974 break;
11977 // At the time of loading start, we don't have timing object, record time.
11979 if (READYSTATE_INTERACTIVE == aReadyState &&
11980 NodePrincipal()->IsSystemPrincipal()) {
11981 if (!mXULPersist) {
11982 mXULPersist = new XULPersist(this);
11983 mXULPersist->Init();
11985 if (!mChromeObserver) {
11986 mChromeObserver = new ChromeObserver(this);
11987 mChromeObserver->Init();
11991 if (aUpdateTimingInformation) {
11992 RecordNavigationTiming(aReadyState);
11995 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
11996 this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
11997 asyncDispatcher->RunDOMEventWhenSafe();
12000 void Document::GetReadyState(nsAString& aReadyState) const {
12001 switch (mReadyState) {
12002 case READYSTATE_LOADING:
12003 aReadyState.AssignLiteral(u"loading");
12004 break;
12005 case READYSTATE_INTERACTIVE:
12006 aReadyState.AssignLiteral(u"interactive");
12007 break;
12008 case READYSTATE_COMPLETE:
12009 aReadyState.AssignLiteral(u"complete");
12010 break;
12011 default:
12012 aReadyState.AssignLiteral(u"uninitialized");
12016 void Document::SuppressEventHandling(uint32_t aIncrease) {
12017 mEventsSuppressed += aIncrease;
12018 if (mEventsSuppressed == aIncrease) {
12019 WindowGlobalChild* wgc = GetWindowGlobalChild();
12020 if (wgc) {
12021 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12024 UpdateFrameRequestCallbackSchedulingState();
12025 for (uint32_t i = 0; i < aIncrease; ++i) {
12026 ScriptLoader()->AddExecuteBlocker();
12029 auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
12030 aSubDoc.SuppressEventHandling(aIncrease);
12031 return CallState::Continue;
12034 EnumerateSubDocuments(suppressInSubDoc);
12037 void Document::NotifyAbortedLoad() {
12038 // If we still have outstanding work blocking DOMContentLoaded,
12039 // then don't try to change the readystate now, but wait until
12040 // they finish and then do so.
12041 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12042 mSetCompleteAfterDOMContentLoaded = true;
12043 return;
12046 // Otherwise we're fully done at this point, so set the
12047 // readystate to complete.
12048 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12049 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12053 static void FireOrClearDelayedEvents(nsTArray<nsCOMPtr<Document>>& aDocuments,
12054 bool aFireEvents) {
12055 nsFocusManager* fm = nsFocusManager::GetFocusManager();
12056 if (!fm) return;
12058 for (uint32_t i = 0; i < aDocuments.Length(); ++i) {
12059 // NB: Don't bother trying to fire delayed events on documents that were
12060 // closed before this event ran.
12061 if (!aDocuments[i]->EventHandlingSuppressed()) {
12062 fm->FireDelayedEvents(aDocuments[i]);
12063 RefPtr<PresShell> presShell = aDocuments[i]->GetPresShell();
12064 if (presShell) {
12065 // Only fire events for active documents.
12066 bool fire = aFireEvents && aDocuments[i]->GetInnerWindow() &&
12067 aDocuments[i]->GetInnerWindow()->IsCurrentInnerWindow();
12068 presShell->FireOrClearDelayedEvents(fire);
12070 aDocuments[i]->FireOrClearPostMessageEvents(aFireEvents);
12075 void Document::PreloadPictureClosed() {
12076 MOZ_ASSERT(mPreloadPictureDepth > 0);
12077 mPreloadPictureDepth--;
12078 if (mPreloadPictureDepth == 0) {
12079 mPreloadPictureFoundSource.SetIsVoid(true);
12083 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12084 const nsAString& aSizesAttr,
12085 const nsAString& aTypeAttr,
12086 const nsAString& aMediaAttr) {
12087 // Nested pictures are not valid syntax, so while we'll eventually load them,
12088 // it's not worth tracking sources mixed between nesting levels to preload
12089 // them effectively.
12090 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12091 // <picture> selects the first matching source, so if this returns a URI we
12092 // needn't consider new sources until a new <picture> is encountered.
12093 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12094 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12095 aMediaAttr, mPreloadPictureFoundSource);
12096 if (found && mPreloadPictureFoundSource.IsVoid()) {
12097 // Found an empty source, which counts
12098 mPreloadPictureFoundSource.SetIsVoid(false);
12103 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12104 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12105 const nsAString& aSizesAttr, bool* aIsImgSet) {
12106 nsString sourceURL;
12107 bool isImgSet;
12108 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12109 // We're in a <picture> element and found a URI from a source previous to
12110 // this image, use it.
12111 sourceURL = mPreloadPictureFoundSource;
12112 isImgSet = true;
12113 } else {
12114 // Otherwise try to use this <img> as a source
12115 HTMLImageElement::SelectSourceForTagWithAttrs(
12116 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12117 VoidString(), sourceURL);
12118 isImgSet = !aSrcsetAttr.IsEmpty();
12121 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12122 if (sourceURL.IsEmpty()) {
12123 return nullptr;
12126 // Construct into URI using passed baseURI (the parser may know of base URI
12127 // changes that have not reached us)
12128 nsresult rv;
12129 nsCOMPtr<nsIURI> uri;
12130 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12131 this, aBaseURI);
12132 if (NS_FAILED(rv)) {
12133 return nullptr;
12136 *aIsImgSet = isImgSet;
12138 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12139 // this this <picture> share the same <sources> (though this is not valid per
12140 // spec)
12141 return uri.forget();
12144 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12145 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12146 bool aLinkPreload) {
12147 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12148 nsIRequest::LOAD_RECORD_START_REQUEST_DELAY |
12149 nsContentUtils::CORSModeToLoadImageFlags(
12150 Element::StringToCORSMode(aCrossOriginAttr));
12152 nsContentPolicyType policyType =
12153 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12154 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12156 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12157 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12159 RefPtr<imgRequestProxy> request;
12160 nsresult rv = nsContentUtils::LoadImage(
12161 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12162 nullptr /* no observer */, loadFlags,
12163 aLinkPreload ? u"link"_ns : u"img"_ns, getter_AddRefs(request),
12164 policyType, false /* urgent */, aLinkPreload);
12166 // Pin image-reference to avoid evicting it from the img-cache before
12167 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12168 // unlink
12169 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12170 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12174 void Document::MaybePreLoadImage(nsIURI* aUri,
12175 const nsAString& aCrossOriginAttr,
12176 ReferrerPolicyEnum aReferrerPolicy,
12177 bool aIsImgSet, bool aLinkPreload,
12178 const TimeStamp& aInitTimestamp) {
12179 if (aLinkPreload) {
12180 // Check if the image was already preloaded in this document to avoid
12181 // duplicate preloading.
12182 PreloadHashKey key = PreloadHashKey::CreateAsImage(
12183 aUri, NodePrincipal(),
12184 dom::Element::StringToCORSMode(aCrossOriginAttr));
12185 if (!mPreloadService.PreloadExists(key)) {
12186 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12187 aLinkPreload);
12189 return;
12192 // Early exit if the img is already present in the img-cache
12193 // which indicates that the "real" load has already started and
12194 // that we shouldn't preload it.
12195 if (nsContentUtils::IsImageInCache(aUri, this)) {
12196 return;
12199 #ifdef NIGHTLY_BUILD
12200 Telemetry::Accumulate(
12201 Telemetry::DOCUMENT_PRELOAD_IMAGE_ASYNCOPEN_DELAY,
12202 static_cast<uint32_t>(
12203 (TimeStamp::Now() - aInitTimestamp).ToMilliseconds()));
12204 #endif
12206 // Image not in cache - trigger preload
12207 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12208 aLinkPreload);
12211 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12212 NS_MutateURI mutator(aOrigURI);
12213 if (NS_FAILED(mutator.GetStatus())) {
12214 return;
12217 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12218 // which ignores the path and uses only the origin. The other is for the
12219 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12220 // non-Anonymous preconnects create different connections on the wire and
12221 // therefore should not be considred duplicates of each other and we
12222 // normalize the path before putting it in the hash to accomplish that.
12224 if (aCORSMode == CORS_ANONYMOUS) {
12225 mutator.SetPathQueryRef("/anonymous"_ns);
12226 } else {
12227 mutator.SetPathQueryRef("/"_ns);
12230 nsCOMPtr<nsIURI> uri;
12231 nsresult rv = mutator.Finalize(uri);
12232 if (NS_FAILED(rv)) {
12233 return;
12236 const bool existingEntryFound =
12237 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12238 if (entry) {
12239 return true;
12241 entry.Insert(true);
12242 return false;
12244 if (existingEntryFound) {
12245 return;
12248 nsCOMPtr<nsISpeculativeConnect> speculator(
12249 do_QueryInterface(nsContentUtils::GetIOService()));
12250 if (!speculator) {
12251 return;
12254 if (aCORSMode == CORS_ANONYMOUS) {
12255 speculator->SpeculativeAnonymousConnect(uri, NodePrincipal(), nullptr);
12256 } else {
12257 speculator->SpeculativeConnect(uri, NodePrincipal(), nullptr);
12261 void Document::ForgetImagePreload(nsIURI* aURI) {
12262 // Checking count is faster than hashing the URI in the common
12263 // case of empty table.
12264 if (mPreloadingImages.Count() != 0) {
12265 nsCOMPtr<imgIRequest> req;
12266 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12267 if (req) {
12268 // Make sure to cancel the request so imagelib knows it's gone.
12269 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12274 void Document::UpdateDocumentStates(EventStates aMaybeChangedStates,
12275 bool aNotify) {
12276 EventStates oldStates = mDocumentState;
12277 if (aMaybeChangedStates.HasState(NS_DOCUMENT_STATE_RTL_LOCALE)) {
12278 if (IsDocumentRightToLeft()) {
12279 mDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
12280 } else {
12281 mDocumentState &= ~NS_DOCUMENT_STATE_RTL_LOCALE;
12285 if (aMaybeChangedStates.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
12286 if (IsTopLevelWindowInactive()) {
12287 mDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
12288 } else {
12289 mDocumentState &= ~NS_DOCUMENT_STATE_WINDOW_INACTIVE;
12293 EventStates changedStates = oldStates ^ mDocumentState;
12294 if (aNotify && !changedStates.IsEmpty()) {
12295 if (PresShell* ps = GetObservingPresShell()) {
12296 ps->DocumentStatesChanged(changedStates);
12301 namespace {
12304 * Stub for LoadSheet(), since all we want is to get the sheet into
12305 * the CSSLoader's style cache
12307 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12308 ~StubCSSLoaderObserver() = default;
12310 public:
12311 NS_IMETHOD
12312 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12313 NS_DECL_ISUPPORTS
12315 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12317 } // namespace
12319 SheetPreloadStatus Document::PreloadStyle(
12320 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12321 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aIntegrity,
12322 css::StylePreloadKind aKind) {
12323 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12325 // The CSSLoader will retain this object after we return.
12326 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12328 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12329 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12331 // Charset names are always ASCII.
12332 auto result = CSSLoader()->LoadSheet(
12333 uri, aKind, aEncoding, referrerInfo, obs,
12334 Element::StringToCORSMode(aCrossOriginAttr), aIntegrity);
12335 if (result.isErr()) {
12336 return SheetPreloadStatus::Errored;
12338 RefPtr<StyleSheet> sheet = result.unwrap();
12339 if (sheet->IsComplete()) {
12340 return SheetPreloadStatus::AlreadyComplete;
12342 return SheetPreloadStatus::InProgress;
12345 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12346 return CSSLoader()
12347 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12348 .unwrapOr(nullptr);
12351 void Document::ResetDocumentDirection() {
12352 if (!nsContentUtils::IsChromeDoc(this)) {
12353 return;
12355 UpdateDocumentStates(NS_DOCUMENT_STATE_RTL_LOCALE, true);
12358 bool Document::IsDocumentRightToLeft() {
12359 if (!nsContentUtils::IsChromeDoc(this)) {
12360 return false;
12362 // setting the localedir attribute on the root element forces a
12363 // specific direction for the document.
12364 Element* element = GetRootElement();
12365 if (element) {
12366 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12367 nullptr};
12368 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12369 strings, eCaseMatters)) {
12370 case 0:
12371 return false;
12372 case 1:
12373 return true;
12374 default:
12375 break; // otherwise, not a valid value, so fall through
12379 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12380 !mDocumentURI->SchemeIs("resource")) {
12381 return false;
12384 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12387 class nsDelayedEventDispatcher : public Runnable {
12388 public:
12389 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12390 : mozilla::Runnable("nsDelayedEventDispatcher"),
12391 mDocuments(std::move(aDocuments)) {}
12392 virtual ~nsDelayedEventDispatcher() = default;
12394 NS_IMETHOD Run() override {
12395 FireOrClearDelayedEvents(mDocuments, true);
12396 return NS_OK;
12399 private:
12400 nsTArray<nsCOMPtr<Document>> mDocuments;
12403 static void GetAndUnsuppressSubDocuments(
12404 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12405 if (aDocument.EventHandlingSuppressed() > 0) {
12406 aDocument.DecreaseEventSuppression();
12407 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12409 aDocuments.AppendElement(&aDocument);
12410 auto recurse = [&aDocuments](Document& aSubDoc) {
12411 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12412 return CallState::Continue;
12414 aDocument.EnumerateSubDocuments(recurse);
12417 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12418 nsTArray<nsCOMPtr<Document>> documents;
12419 GetAndUnsuppressSubDocuments(*this, documents);
12421 for (nsCOMPtr<Document>& doc : documents) {
12422 if (!doc->EventHandlingSuppressed()) {
12423 WindowGlobalChild* wgc = doc->GetWindowGlobalChild();
12424 if (wgc) {
12425 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12428 MOZ_ASSERT(NS_IsMainThread());
12429 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12430 std::move(doc->mSuspendedQueues);
12431 for (net::ChannelEventQueue* queue : queues) {
12432 queue->Resume();
12435 // If there have been any events driven by the refresh driver which were
12436 // delayed due to events being suppressed in this document, make sure
12437 // there is a refresh scheduled soon so the events will run.
12438 if (doc->mHasDelayedRefreshEvent) {
12439 doc->mHasDelayedRefreshEvent = false;
12441 if (doc->mPresShell) {
12442 nsRefreshDriver* rd =
12443 doc->mPresShell->GetPresContext()->RefreshDriver();
12444 rd->RunDelayedEventsSoon();
12450 if (aFireEvents) {
12451 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12452 nsCOMPtr<nsIRunnable> ded =
12453 new nsDelayedEventDispatcher(std::move(documents));
12454 Dispatch(TaskCategory::Other, ded.forget());
12455 } else {
12456 FireOrClearDelayedEvents(documents, false);
12460 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12461 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12464 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12465 MOZ_ASSERT(NS_IsMainThread());
12466 MOZ_ASSERT(EventHandlingSuppressed());
12467 mSuspendedQueues.AppendElement(aQueue);
12470 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12471 MOZ_ASSERT(NS_IsMainThread());
12473 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12474 mSuspendedPostMessageEvents.AppendElement(aEvent);
12475 return true;
12477 return false;
12480 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12481 nsTArray<RefPtr<PostMessageEvent>> events =
12482 std::move(mSuspendedPostMessageEvents);
12484 if (aFireEvents) {
12485 for (PostMessageEvent* event : events) {
12486 event->Run();
12491 void Document::SetSuppressedEventListener(EventListener* aListener) {
12492 mSuppressedEventListener = aListener;
12493 auto setOnSubDocs = [&](Document& aDocument) {
12494 aDocument.SetSuppressedEventListener(aListener);
12495 return CallState::Continue;
12497 EnumerateSubDocuments(setOnSubDocs);
12500 nsISupports* Document::GetCurrentContentSink() {
12501 return mParser ? mParser->GetContentSink() : nullptr;
12504 Document* Document::GetTemplateContentsOwner() {
12505 if (!mTemplateContentsOwner) {
12506 bool hasHadScriptObject = true;
12507 nsIScriptGlobalObject* scriptObject =
12508 GetScriptHandlingObject(hasHadScriptObject);
12510 nsCOMPtr<Document> document;
12511 nsresult rv = NS_NewDOMDocument(
12512 getter_AddRefs(document),
12513 u""_ns, // aNamespaceURI
12514 u""_ns, // aQualifiedName
12515 nullptr, // aDoctype
12516 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12517 true, // aLoadedAsData
12518 scriptObject, // aEventObject
12519 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12520 NS_ENSURE_SUCCESS(rv, nullptr);
12522 mTemplateContentsOwner = document;
12523 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12525 if (!scriptObject) {
12526 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12529 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12531 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12532 // that it is the template contents owner of nested template elements.
12533 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12536 return mTemplateContentsOwner;
12539 static already_AddRefed<nsPIDOMWindowOuter> FindTopWindowForElement(
12540 Element* element) {
12541 Document* document = element->OwnerDoc();
12542 if (!document) {
12543 return nullptr;
12546 nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow();
12547 if (!window) {
12548 return nullptr;
12551 // Trying to find the top window (equivalent to window.top).
12552 if (nsCOMPtr<nsPIDOMWindowOuter> top = window->GetInProcessTop()) {
12553 window = std::move(top);
12555 return window.forget();
12559 * nsAutoFocusEvent is used to dispatch a focus event for an
12560 * nsGenericHTMLFormElement with the autofocus attribute enabled.
12562 class nsAutoFocusEvent : public Runnable {
12563 public:
12564 explicit nsAutoFocusEvent(nsCOMPtr<Element>&& aElement,
12565 nsCOMPtr<nsPIDOMWindowOuter>&& aTopWindow)
12566 : mozilla::Runnable("nsAutoFocusEvent"),
12567 mElement(std::move(aElement)),
12568 mTopWindow(std::move(aTopWindow)) {}
12570 NS_IMETHOD Run() override {
12571 nsCOMPtr<nsPIDOMWindowOuter> currentTopWindow =
12572 FindTopWindowForElement(mElement);
12573 if (currentTopWindow != mTopWindow) {
12574 // The element's top window changed from when the event was queued.
12575 // Don't take away focus from an unrelated window.
12576 return NS_OK;
12579 if (Document* doc = mTopWindow->GetExtantDoc()) {
12580 if (doc->IsAutoFocusFired()) {
12581 return NS_OK;
12583 doc->SetAutoFocusFired();
12586 // Don't steal focus from the user.
12587 if (mTopWindow->GetFocusedElement()) {
12588 return NS_OK;
12591 FocusOptions options;
12592 ErrorResult rv;
12593 mElement->Focus(options, CallerType::System, rv);
12594 return rv.StealNSResult();
12597 private:
12598 nsCOMPtr<Element> mElement;
12599 nsCOMPtr<nsPIDOMWindowOuter> mTopWindow;
12602 void Document::SetAutoFocusElement(Element* aAutoFocusElement) {
12603 if (mAutoFocusFired) {
12604 // Too late.
12605 return;
12608 if (mAutoFocusElement) {
12609 // The spec disallows multiple autofocus elements, so we consider only the
12610 // first one to preserve the old behavior.
12611 return;
12614 mAutoFocusElement = do_GetWeakReference(aAutoFocusElement);
12615 TriggerAutoFocus();
12618 void Document::SetAutoFocusFired() { mAutoFocusFired = true; }
12620 bool Document::IsAutoFocusFired() { return mAutoFocusFired; }
12622 void Document::TriggerAutoFocus() {
12623 if (mAutoFocusFired) {
12624 return;
12627 if (!mPresShell || !mPresShell->DidInitialize()) {
12628 // Delay autofocus until frames are constructed so that we don't thrash
12629 // style and layout calculations.
12630 return;
12633 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(mAutoFocusElement);
12634 if (autoFocusElement && autoFocusElement->OwnerDoc() == this) {
12635 nsCOMPtr<nsPIDOMWindowOuter> topWindow =
12636 FindTopWindowForElement(autoFocusElement);
12637 if (!topWindow) {
12638 return;
12641 // NOTE: This may be removed in the future since the spec technically
12642 // allows autofocus after load.
12643 nsCOMPtr<Document> topDoc = topWindow->GetExtantDoc();
12644 if (topDoc &&
12645 topDoc->GetReadyStateEnum() == Document::READYSTATE_COMPLETE) {
12646 return;
12649 nsCOMPtr<nsIRunnable> event =
12650 new nsAutoFocusEvent(std::move(autoFocusElement), topWindow.forget());
12651 nsresult rv = NS_DispatchToCurrentThread(event.forget());
12652 NS_ENSURE_SUCCESS_VOID(rv);
12656 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
12657 if (!aDocumentURI) {
12658 return;
12661 nsAutoCString ref;
12663 // Since all URI's that pass through here aren't URL's we can't
12664 // rely on the nsIURI implementation for providing a way for
12665 // finding the 'ref' part of the URI, we'll haveto revert to
12666 // string routines for finding the data past '#'
12668 nsresult rv = aDocumentURI->GetSpec(ref);
12669 if (NS_FAILED(rv)) {
12670 Unused << aDocumentURI->GetRef(mScrollToRef);
12671 return;
12674 nsReadingIterator<char> start, end;
12676 ref.BeginReading(start);
12677 ref.EndReading(end);
12679 if (FindCharInReadable('#', start, end)) {
12680 ++start; // Skip over the '#'
12682 mScrollToRef = Substring(start, end);
12686 void Document::ScrollToRef() {
12687 if (mScrolledToRefAlready) {
12688 RefPtr<PresShell> presShell = GetPresShell();
12689 if (presShell) {
12690 presShell->ScrollToAnchor();
12692 return;
12695 if (mScrollToRef.IsEmpty()) {
12696 return;
12699 RefPtr<PresShell> presShell = GetPresShell();
12700 if (presShell) {
12701 nsresult rv = NS_ERROR_FAILURE;
12702 // We assume that the bytes are in UTF-8, as it says in the spec:
12703 // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
12704 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
12705 // Check an empty string which might be caused by the UTF-8 conversion
12706 if (!ref.IsEmpty()) {
12707 // Note that GoToAnchor will handle flushing layout as needed.
12708 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
12709 } else {
12710 rv = NS_ERROR_FAILURE;
12713 if (NS_FAILED(rv)) {
12714 nsAutoCString buff;
12715 const bool unescaped =
12716 NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
12717 /*aFlags =*/0, buff);
12719 // This attempt is only necessary if characters were unescaped.
12720 if (unescaped) {
12721 NS_ConvertUTF8toUTF16 utf16Str(buff);
12722 if (!utf16Str.IsEmpty()) {
12723 rv = presShell->GoToAnchor(utf16Str,
12724 mChangeScrollPosWhenScrollingToRef);
12728 // If UTF-8 URI failed then try to assume the string as a
12729 // document's charset.
12730 if (NS_FAILED(rv)) {
12731 const Encoding* encoding = GetDocumentCharacterSet();
12732 rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
12733 ref);
12734 if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
12735 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
12739 if (NS_SUCCEEDED(rv)) {
12740 mScrolledToRefAlready = true;
12745 void Document::RegisterActivityObserver(nsISupports* aSupports) {
12746 if (!mActivityObservers) {
12747 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
12749 mActivityObservers->Insert(aSupports);
12752 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
12753 if (!mActivityObservers) {
12754 return false;
12756 return mActivityObservers->EnsureRemoved(aSupports);
12759 void Document::EnumerateActivityObservers(
12760 ActivityObserverEnumerator aEnumerator) {
12761 if (!mActivityObservers) {
12762 return;
12765 const auto keyArray =
12766 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
12767 for (auto& observer : keyArray) {
12768 aEnumerator(observer.get());
12772 void Document::RegisterPendingLinkUpdate(Link* aLink) {
12773 if (aLink->HasPendingLinkUpdate()) {
12774 return;
12777 aLink->SetHasPendingLinkUpdate();
12779 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
12780 nsCOMPtr<nsIRunnable> event =
12781 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
12782 &Document::FlushPendingLinkUpdates);
12783 // Do this work in a second in the worst case.
12784 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
12785 EventQueuePriority::Idle);
12786 if (NS_FAILED(rv)) {
12787 // If during shutdown posting a runnable doesn't succeed, we probably
12788 // don't need to update link states.
12789 return;
12791 mHasLinksToUpdateRunnable = true;
12794 mLinksToUpdate.InfallibleAppend(aLink);
12797 void Document::FlushPendingLinkUpdates() {
12798 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
12799 MOZ_ASSERT(mHasLinksToUpdateRunnable);
12800 mHasLinksToUpdateRunnable = false;
12802 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
12803 mFlushingPendingLinkUpdates = true;
12805 while (!mLinksToUpdate.IsEmpty()) {
12806 LinksToUpdateList links(std::move(mLinksToUpdate));
12807 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
12808 Link* link = iter.Get();
12809 Element* element = link->GetElement();
12810 if (element->OwnerDoc() == this) {
12811 link->ClearHasPendingLinkUpdate();
12812 if (element->IsInComposedDoc()) {
12813 element->UpdateLinkState(link->LinkState());
12821 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
12822 * which is a node in the original document from which aStaticClone was cloned.
12824 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
12825 Document& aStaticClone) {
12826 MOZ_ASSERT(aOrigNode);
12828 // Selections in anonymous subtrees aren't supported.
12829 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
12830 return nullptr;
12833 // If the node is disconnected, this is a bug in the selection code, but it
12834 // can happen with shadow DOM so handle it.
12835 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
12836 return nullptr;
12839 nsTArray<int32_t> indexArray;
12840 const nsINode* current = aOrigNode;
12841 while (const nsINode* parent = current->GetParentNode()) {
12842 int32_t index = parent->ComputeIndexOf(current);
12843 MOZ_ASSERT(index >= 0);
12844 indexArray.AppendElement(index);
12845 current = parent;
12847 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
12848 nsINode* correspondingNode = [&]() -> nsINode* {
12849 if (current->IsDocument()) {
12850 return &aStaticClone;
12852 const auto* shadow = ShadowRoot::FromNode(*current);
12853 if (!shadow) {
12854 return nullptr;
12856 nsINode* correspondingHost =
12857 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
12858 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
12859 return nullptr;
12861 return correspondingHost->AsElement()->GetShadowRoot();
12862 }();
12864 if (NS_WARN_IF(!correspondingNode)) {
12865 return nullptr;
12867 for (int32_t i : Reversed(indexArray)) {
12868 correspondingNode = correspondingNode->GetChildAt_Deprecated(i);
12869 NS_ENSURE_TRUE(correspondingNode, nullptr);
12871 return correspondingNode;
12875 * Caches the selection ranges from the source document onto the static clone in
12876 * case the "Print Selection Only" functionality is invoked.
12878 * Note that we cannot use the selection obtained from GetOriginalDocument()
12879 * since that selection may have mutated after the print was invoked.
12881 * Note also that because nsRange objects point into a specific document's
12882 * nodes, we cannot reuse an array of nsRange objects across multiple static
12883 * clone documents. For that reason we cache a new array of ranges on each
12884 * static clone that we create.
12886 * TODO(emilio): This can be simplified once we don't re-clone from static
12887 * documents.
12889 * @param aSourceDoc the document from which we are caching selection ranges
12890 * @param aStaticClone the document that will hold the cache
12891 * @return true if a selection range was cached
12893 static void CachePrintSelectionRanges(const Document& aSourceDoc,
12894 Document& aStaticClone) {
12895 MOZ_ASSERT(aStaticClone.IsStaticDocument());
12896 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
12898 const Selection* origSelection = nullptr;
12899 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
12900 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
12902 if (sourceDocIsStatic) {
12903 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
12904 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
12905 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
12906 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
12909 if (!origSelection && !origRanges) {
12910 return;
12913 size_t rangeCount =
12914 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
12915 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
12917 for (size_t i = 0; i < rangeCount; ++i) {
12918 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
12919 : origSelection->GetRangeAt(i);
12920 nsINode* startContainer = range->GetStartContainer();
12921 nsINode* endContainer = range->GetEndContainer();
12923 if (!startContainer || !endContainer) {
12924 continue;
12927 nsINode* startNode =
12928 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
12929 nsINode* endNode =
12930 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
12932 if (NS_WARN_IF(!startNode || !endNode)) {
12933 continue;
12936 RefPtr<nsRange> clonedRange =
12937 nsRange::Create(startNode, range->StartOffset(), endNode,
12938 range->EndOffset(), IgnoreErrors());
12939 if (clonedRange && !clonedRange->Collapsed()) {
12940 printRanges->AppendElement(std::move(clonedRange));
12944 if (printRanges->IsEmpty()) {
12945 return;
12948 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
12949 printRanges.release(),
12950 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
12953 already_AddRefed<Document> Document::CreateStaticClone(
12954 nsIDocShell* aCloneContainer, nsIContentViewer* aViewer,
12955 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
12956 MOZ_ASSERT(!mCreatingStaticClone);
12957 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
12958 MOZ_DIAGNOSTIC_ASSERT(aViewer);
12960 mCreatingStaticClone = true;
12961 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
12962 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
12964 auto raii = MakeScopeExit([&] {
12965 RemoveProperty(nsGkAtoms::adoptedsheetclones);
12966 mCreatingStaticClone = false;
12969 // Make document use different container during cloning.
12971 // FIXME(emilio): Why is this needed?
12972 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
12973 SetContainer(nsDocShell::Cast(aCloneContainer));
12974 IgnoredErrorResult rv;
12975 nsCOMPtr<nsINode> clonedNode = this->CloneNode(true, rv);
12976 SetContainer(originalShell);
12977 if (rv.Failed()) {
12978 return nullptr;
12981 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
12982 if (!clonedDoc) {
12983 return nullptr;
12986 size_t sheetsCount = SheetCount();
12987 for (size_t i = 0; i < sheetsCount; ++i) {
12988 RefPtr<StyleSheet> sheet = SheetAt(i);
12989 if (sheet) {
12990 if (sheet->IsApplicable()) {
12991 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
12992 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
12993 if (clonedSheet) {
12994 clonedDoc->AddStyleSheet(clonedSheet);
12999 clonedDoc->CloneAdoptedSheetsFrom(*this);
13001 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13002 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13003 for (StyleSheet* sheet : sheets) {
13004 if (sheet->IsApplicable()) {
13005 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13006 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13007 if (clonedSheet) {
13008 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13009 clonedSheet);
13015 // Font faces created with the JS API will not be reflected in the
13016 // stylesheets and need to be copied over to the cloned document.
13017 if (const FontFaceSet* set = GetFonts()) {
13018 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13021 clonedDoc->mReferrerInfo =
13022 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13023 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13024 CachePrintSelectionRanges(*this, *clonedDoc);
13026 // We're done with the clone, embed ourselves into the document viewer and
13027 // clone our children. The order here is pretty important, because our
13028 // document our document needs to have an owner global before we can create
13029 // the frame loaders for subdocuments.
13030 aViewer->SetDocument(clonedDoc);
13032 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13034 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13035 for (const auto& clone : pendingClones) {
13036 RefPtr<Element> element = do_QueryObject(clone.mElement);
13037 RefPtr<nsFrameLoader> frameLoader =
13038 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13040 if (NS_WARN_IF(!frameLoader)) {
13041 continue;
13044 clone.mElement->SetFrameLoader(frameLoader);
13046 nsresult rv = frameLoader->FinishStaticClone(
13047 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13048 Unused << NS_WARN_IF(NS_FAILED(rv));
13051 return clonedDoc.forget();
13054 void Document::UnlinkOriginalDocumentIfStatic() {
13055 if (IsStaticDocument() && mOriginalDocument) {
13056 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13057 mOriginalDocument->mStaticCloneCount--;
13058 mOriginalDocument = nullptr;
13060 MOZ_ASSERT(!mOriginalDocument);
13063 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13064 int32_t* aHandle) {
13065 if (mFrameRequestCallbackCounter == INT32_MAX) {
13066 // Can't increment without overflowing; bail out
13067 return NS_ERROR_NOT_AVAILABLE;
13069 int32_t newHandle = ++mFrameRequestCallbackCounter;
13071 mFrameRequestCallbacks.AppendElement(FrameRequest(aCallback, newHandle));
13072 UpdateFrameRequestCallbackSchedulingState();
13074 *aHandle = newHandle;
13075 return NS_OK;
13078 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13079 // mFrameRequestCallbacks is stored sorted by handle
13080 if (mFrameRequestCallbacks.RemoveElementSorted(aHandle)) {
13081 UpdateFrameRequestCallbackSchedulingState();
13082 } else {
13083 Unused << mCanceledFrameRequestCallbacks.put(aHandle);
13087 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13088 return !mCanceledFrameRequestCallbacks.empty() &&
13089 mCanceledFrameRequestCallbacks.has(aHandle);
13092 nsresult Document::GetStateObject(nsIVariant** aState) {
13093 // Get the document's current state object. This is the object backing both
13094 // history.state and popStateEvent.state.
13096 // mStateObjectContainer may be null; this just means that there's no
13097 // current state object.
13099 if (!mStateObjectCached && mStateObjectContainer) {
13100 AutoJSAPI jsapi;
13101 // Init with null is "OK" in the sense that it will just fail.
13102 if (!jsapi.Init(GetScopeObject())) {
13103 return NS_ERROR_UNEXPECTED;
13105 mStateObjectContainer->DeserializeToVariant(
13106 jsapi.cx(), getter_AddRefs(mStateObjectCached));
13109 NS_IF_ADDREF(*aState = mStateObjectCached);
13110 return NS_OK;
13113 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13114 mTiming = aTiming;
13115 if (!mLoadingTimeStamp.IsNull() && mTiming) {
13116 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), mLoadingTimeStamp);
13120 nsContentList* Document::ImageMapList() {
13121 if (!mImageMaps) {
13122 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13123 nsGkAtoms::map);
13126 return mImageMaps;
13129 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13130 static const char* kDeprecationWarnings[] = {
13131 #include "nsDeprecatedOperationList.h"
13132 nullptr};
13133 #undef DEPRECATED_OPERATION
13135 #define DOCUMENT_WARNING(_op) #_op "Warning",
13136 static const char* kDocumentWarnings[] = {
13137 #include "nsDocumentWarningList.h"
13138 nullptr};
13139 #undef DOCUMENT_WARNING
13141 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13142 switch (aOperation) {
13143 #define DEPRECATED_OPERATION(_op) \
13144 case DeprecatedOperations::e##_op: \
13145 return eUseCounter_##_op;
13146 #include "nsDeprecatedOperationList.h"
13147 #undef DEPRECATED_OPERATION
13148 default:
13149 MOZ_CRASH();
13153 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13154 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13157 void Document::WarnOnceAbout(
13158 DeprecatedOperations aOperation, bool asError /* = false */,
13159 const nsTArray<nsString>& aParams /* = empty array */) const {
13160 MOZ_ASSERT(NS_IsMainThread());
13161 if (HasWarnedAbout(aOperation)) {
13162 return;
13164 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13165 // Don't count deprecated operations for about pages since those pages
13166 // are almost in our control, and we always need to remove uses there
13167 // before we remove the operation itself anyway.
13168 if (!IsAboutPage()) {
13169 const_cast<Document*>(this)->SetUseCounter(
13170 OperationToUseCounter(aOperation));
13172 uint32_t flags =
13173 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13174 nsContentUtils::ReportToConsole(
13175 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13176 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13179 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13180 return mDocWarningWarnedAbout[aWarning];
13183 void Document::WarnOnceAbout(
13184 DocumentWarnings aWarning, bool asError /* = false */,
13185 const nsTArray<nsString>& aParams /* = empty array */) const {
13186 MOZ_ASSERT(NS_IsMainThread());
13187 if (HasWarnedAbout(aWarning)) {
13188 return;
13190 mDocWarningWarnedAbout[aWarning] = true;
13191 uint32_t flags =
13192 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13193 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13194 nsContentUtils::eDOM_PROPERTIES,
13195 kDocumentWarnings[aWarning], aParams);
13198 mozilla::dom::ImageTracker* Document::ImageTracker() {
13199 if (!mImageTracker) {
13200 mImageTracker = new mozilla::dom::ImageTracker;
13202 return mImageTracker;
13205 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13206 SVGUseElement& aUseElement) {
13207 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13209 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13210 // Printing doesn't deal well with dynamic DOM mutations.
13211 return;
13214 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13216 if (PresShell* presShell = GetPresShell()) {
13217 presShell->EnsureStyleFlush();
13221 void Document::DoUpdateSVGUseElementShadowTrees() {
13222 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13224 do {
13225 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13226 mSVGUseElementsNeedingShadowTreeUpdate);
13227 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13229 for (const auto& useElement : useElementsToUpdate) {
13230 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13231 // The element was in another <use> shadow tree which we processed
13232 // already and also needed an update, and is removed from the document
13233 // now, so nothing to do here.
13234 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13235 continue;
13237 useElement->UpdateShadowTree();
13239 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13242 void Document::NotifyMediaFeatureValuesChanged() {
13243 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13244 imageElement->MediaFeatureValuesChanged();
13248 already_AddRefed<Touch> Document::CreateTouch(
13249 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13250 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13251 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13252 float aRotationAngle, float aForce) {
13253 RefPtr<Touch> touch =
13254 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13255 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13256 return touch.forget();
13259 already_AddRefed<TouchList> Document::CreateTouchList() {
13260 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13261 return retval.forget();
13264 already_AddRefed<TouchList> Document::CreateTouchList(
13265 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13266 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13267 retval->Append(&aTouch);
13268 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13269 retval->Append(aTouches[i].get());
13271 return retval.forget();
13274 already_AddRefed<TouchList> Document::CreateTouchList(
13275 const Sequence<OwningNonNull<Touch>>& aTouches) {
13276 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13277 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13278 retval->Append(aTouches[i].get());
13280 return retval.forget();
13283 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13284 float aX, float aY) {
13285 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13287 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13288 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13289 nsPoint pt(x, y);
13291 FlushPendingNotifications(FlushType::Layout);
13293 PresShell* presShell = GetPresShell();
13294 if (!presShell) {
13295 return nullptr;
13298 nsIFrame* rootFrame = presShell->GetRootFrame();
13300 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13301 if (!rootFrame) {
13302 return nullptr;
13305 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13306 RelativeTo{rootFrame}, pt,
13307 {{FrameForPointOption::IgnorePaintSuppression,
13308 FrameForPointOption::IgnoreCrossDoc}});
13309 if (!ptFrame) {
13310 return nullptr;
13313 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13314 nsPoint aOffset;
13315 nsCOMPtr<nsIWidget> widget = nsContentUtils::GetWidget(presShell, &aOffset);
13316 LayoutDeviceIntPoint refPoint = nsContentUtils::ToWidgetPoint(
13317 CSSPoint(aX, aY), aOffset, GetPresContext());
13318 nsPoint adjustedPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
13319 widget, refPoint, RelativeTo{ptFrame});
13321 nsIFrame::ContentOffsets offsets =
13322 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13324 nsCOMPtr<nsIContent> node = offsets.content;
13325 uint32_t offset = offsets.offset;
13326 nsCOMPtr<nsIContent> anonNode = node;
13327 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13328 if (nodeIsAnonymous) {
13329 node = ptFrame->GetContent();
13330 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13331 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13332 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13333 if (textFrame) {
13334 // If the anonymous content node has a child, then we need to make sure
13335 // that we get the appropriate child, as otherwise the offset may not be
13336 // correct when we construct a range for it.
13337 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13338 if (firstChild) {
13339 anonNode = firstChild;
13342 if (textArea) {
13343 offset =
13344 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13347 node = nonanon;
13348 } else {
13349 node = nullptr;
13350 offset = 0;
13354 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13355 if (nodeIsAnonymous) {
13356 aCaretPos->SetAnonymousContentNode(anonNode);
13358 return aCaretPos.forget();
13361 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13362 // We rely on correct frame information here, so need to flush frames.
13363 FlushPendingNotifications(FlushType::Frames);
13365 // An element that is the HTML body element is potentially scrollable if all
13366 // of the following conditions are true:
13368 // The element has an associated CSS layout box.
13369 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13370 if (!bodyFrame) {
13371 return false;
13374 // The element's parent element's computed value of the overflow-x and
13375 // overflow-y properties are visible.
13376 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13377 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13378 if (parentFrame &&
13379 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13380 return false;
13383 // The element's computed value of the overflow-x or overflow-y properties is
13384 // not visible.
13385 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13388 Element* Document::GetScrollingElement() {
13389 // Keep this in sync with IsScrollingElement.
13390 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13391 RefPtr<HTMLBodyElement> body = GetBodyElement();
13392 if (body && !IsPotentiallyScrollable(body)) {
13393 return body;
13396 return nullptr;
13399 return GetRootElement();
13402 bool Document::IsScrollingElement(Element* aElement) {
13403 // Keep this in sync with GetScrollingElement.
13404 MOZ_ASSERT(aElement);
13406 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13407 return aElement == GetRootElement();
13410 // In the common case when aElement != body, avoid refcounting.
13411 HTMLBodyElement* body = GetBodyElement();
13412 if (aElement != body) {
13413 return false;
13416 // Now we know body is non-null, since aElement is not null. It's the
13417 // scrolling element for the document if it itself is not potentially
13418 // scrollable.
13419 RefPtr<HTMLBodyElement> strongBody(body);
13420 return !IsPotentiallyScrollable(strongBody);
13423 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13424 public:
13425 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
13426 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13428 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13429 const BlockParsingOptions& aOptions)
13430 : mPromise(aPromise) {
13431 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13432 if (parser &&
13433 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13434 parser->BlockParser();
13435 mParser = do_GetWeakReference(parser);
13436 mDocument = aDocument;
13437 mDocument->BlockOnload();
13438 mDocument->BlockDOMContentLoaded();
13442 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
13443 MaybeUnblockParser();
13445 mPromise->MaybeResolve(aValue);
13448 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
13449 MaybeUnblockParser();
13451 mPromise->MaybeReject(aValue);
13454 protected:
13455 virtual ~UnblockParsingPromiseHandler() {
13456 // If we're being cleaned up by the cycle collector, our mDocument reference
13457 // may have been unlinked while our mParser weak reference is still alive.
13458 if (mDocument) {
13459 MaybeUnblockParser();
13463 private:
13464 void MaybeUnblockParser() {
13465 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13466 if (parser) {
13467 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13468 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13469 if (parser == docParser) {
13470 parser->UnblockParser();
13471 parser->ContinueInterruptedParsingAsync();
13474 if (mDocument) {
13475 // We blocked DOMContentLoaded and load events on this document. Unblock
13476 // them. Note that we want to do that no matter what's going on with the
13477 // parser state for this document. Maybe someone caused it to stop being
13478 // parsed, so CreatorParserOrNull() is returning null, but we still want
13479 // to unblock these.
13480 mDocument->UnblockDOMContentLoaded();
13481 mDocument->UnblockOnload(false);
13483 mParser = nullptr;
13484 mDocument = nullptr;
13487 nsWeakPtr mParser;
13488 RefPtr<Promise> mPromise;
13489 RefPtr<Document> mDocument;
13492 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
13494 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
13495 NS_INTERFACE_MAP_ENTRY(nsISupports)
13496 NS_INTERFACE_MAP_END
13498 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
13499 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
13501 already_AddRefed<Promise> Document::BlockParsing(
13502 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
13503 RefPtr<Promise> resultPromise =
13504 Promise::Create(aPromise.GetParentObject(), aRv);
13505 if (aRv.Failed()) {
13506 return nullptr;
13509 RefPtr<PromiseNativeHandler> promiseHandler =
13510 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
13511 aPromise.AppendNativeHandler(promiseHandler);
13513 return resultPromise.forget();
13516 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
13517 if (mFailedChannel) {
13518 nsCOMPtr<nsIURI> failedURI;
13519 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
13520 return failedURI.forget();
13524 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
13525 if (!uri) {
13526 return nullptr;
13529 return uri.forget();
13532 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
13533 if (mIsGoingAway) {
13534 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13535 return nullptr;
13538 if (!mReadyForIdle) {
13539 nsIGlobalObject* global = GetScopeObject();
13540 if (!global) {
13541 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13542 return nullptr;
13545 mReadyForIdle = Promise::Create(global, aRv);
13546 if (aRv.Failed()) {
13547 return nullptr;
13551 return mReadyForIdle;
13554 void Document::MaybeResolveReadyForIdle() {
13555 IgnoredErrorResult rv;
13556 Promise* readyPromise = GetDocumentReadyForIdle(rv);
13557 if (readyPromise) {
13558 readyPromise->MaybeResolve(this);
13562 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
13563 // The policy is created when the document is initialized. We _must_ have a
13564 // policy here even if the featurePolicy pref is off. If this assertion fails,
13565 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
13566 MOZ_ASSERT(mFeaturePolicy);
13567 return mFeaturePolicy;
13570 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
13571 // Only chrome documents are allowed to use command dispatcher.
13572 if (!nsContentUtils::IsChromeDoc(this)) {
13573 return nullptr;
13575 if (!mCommandDispatcher) {
13576 // Create our command dispatcher and hook it up.
13577 mCommandDispatcher = new nsXULCommandDispatcher(this);
13579 return mCommandDispatcher;
13582 void Document::InitializeXULBroadcastManager() {
13583 if (mXULBroadcastManager) {
13584 return;
13586 mXULBroadcastManager = new XULBroadcastManager(this);
13589 namespace {
13591 class DevToolsMutationObserver final : public nsStubMutationObserver {
13592 NS_DECL_ISUPPORTS
13593 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
13594 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
13595 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
13597 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
13598 // relies on the event firing _before_ the removal happens.
13599 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
13601 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
13602 // data changes right now (maybe intentionally?).
13603 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
13605 DevToolsMutationObserver() = default;
13607 private:
13608 void FireEvent(nsINode* aTarget, const nsAString& aType);
13610 ~DevToolsMutationObserver() = default;
13613 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
13615 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
13616 const nsAString& aType) {
13617 if (aTarget->ChromeOnlyAccess()) {
13618 return;
13620 (new AsyncEventDispatcher(aTarget, aType, CanBubble::eNo,
13621 ChromeOnlyDispatch::eYes, Composed::eYes))
13622 ->RunDOMEventWhenSafe();
13625 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
13626 int32_t aNamespaceID,
13627 nsAtom* aAttribute,
13628 int32_t aModType,
13629 const nsAttrValue* aOldValue) {
13630 FireEvent(aElement, u"devtoolsattrmodified"_ns);
13633 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
13634 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
13635 ContentInserted(c);
13639 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
13640 FireEvent(aChild, u"devtoolschildinserted"_ns);
13643 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
13645 } // namespace
13647 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
13648 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
13649 return;
13651 mDevToolsWatchingDOMMutations = aValue;
13652 if (aValue) {
13653 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
13654 sDevToolsMutationObserver = new DevToolsMutationObserver();
13655 ClearOnShutdown(&sDevToolsMutationObserver);
13657 AddMutationObserver(sDevToolsMutationObserver);
13658 } else if (sDevToolsMutationObserver) {
13659 RemoveMutationObserver(sDevToolsMutationObserver);
13663 void Document::MaybeWarnAboutZoom() {
13664 if (mHasWarnedAboutZoom) {
13665 return;
13667 const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
13668 mStyleUseCounters.get(), eCSSProperty_zoom);
13669 if (!usedZoom) {
13670 return;
13673 mHasWarnedAboutZoom = true;
13674 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
13675 this, nsContentUtils::eLAYOUT_PROPERTIES,
13676 "ZoomPropertyWarning");
13679 nsIHTMLCollection* Document::Children() {
13680 if (!mChildrenCollection) {
13681 mChildrenCollection =
13682 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
13683 nsGkAtoms::_asterisk, false);
13686 return mChildrenCollection;
13689 uint32_t Document::ChildElementCount() { return Children()->Length(); }
13691 // Singleton class to manage the list of fullscreen documents which are the
13692 // root of a branch which contains fullscreen documents. We maintain this list
13693 // so that we can easily exit all windows from fullscreen when the user
13694 // presses the escape key.
13695 class FullscreenRoots {
13696 public:
13697 // Adds the root of given document to the manager. Calling this method
13698 // with a document whose root is already contained has no effect.
13699 static void Add(Document* aDoc);
13701 // Iterates over every root in the root list, and calls aFunction, passing
13702 // each root once to aFunction. It is safe to call Add() and Remove() while
13703 // iterating over the list (i.e. in aFunction). Documents that are removed
13704 // from the manager during traversal are not traversed, and documents that
13705 // are added to the manager during traversal are also not traversed.
13706 static void ForEach(void (*aFunction)(Document* aDoc));
13708 // Removes the root of a specific document from the manager.
13709 static void Remove(Document* aDoc);
13711 // Returns true if all roots added to the list have been removed.
13712 static bool IsEmpty();
13714 private:
13715 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
13716 MOZ_COUNTED_DTOR(FullscreenRoots)
13718 enum { NotFound = uint32_t(-1) };
13719 // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
13720 static uint32_t Find(Document* aRoot);
13722 // Returns true if aRoot is in the list of fullscreen roots.
13723 static bool Contains(Document* aRoot);
13725 // Singleton instance of the FullscreenRoots. This is instantiated when a
13726 // root is added, and it is deleted when the last root is removed.
13727 static FullscreenRoots* sInstance;
13729 // List of weak pointers to roots.
13730 nsTArray<nsWeakPtr> mRoots;
13733 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
13735 /* static */
13736 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
13737 if (!sInstance) {
13738 return;
13740 // Create a copy of the roots array, and iterate over the copy. This is so
13741 // that if an element is removed from mRoots we don't mess up our iteration.
13742 nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
13743 // Call aFunction on all entries.
13744 for (uint32_t i = 0; i < roots.Length(); i++) {
13745 nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
13746 // Check that the root isn't in the manager. This is so that new additions
13747 // while we were running don't get traversed.
13748 if (root && FullscreenRoots::Contains(root)) {
13749 aFunction(root);
13754 /* static */
13755 bool FullscreenRoots::Contains(Document* aRoot) {
13756 return FullscreenRoots::Find(aRoot) != NotFound;
13759 /* static */
13760 void FullscreenRoots::Add(Document* aDoc) {
13761 nsCOMPtr<Document> root =
13762 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
13763 if (!FullscreenRoots::Contains(root)) {
13764 if (!sInstance) {
13765 sInstance = new FullscreenRoots();
13767 sInstance->mRoots.AppendElement(do_GetWeakReference(root));
13771 /* static */
13772 uint32_t FullscreenRoots::Find(Document* aRoot) {
13773 if (!sInstance) {
13774 return NotFound;
13776 nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
13777 for (uint32_t i = 0; i < roots.Length(); i++) {
13778 nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
13779 if (otherRoot == aRoot) {
13780 return i;
13783 return NotFound;
13786 /* static */
13787 void FullscreenRoots::Remove(Document* aDoc) {
13788 nsCOMPtr<Document> root =
13789 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
13790 uint32_t index = Find(root);
13791 NS_ASSERTION(index != NotFound,
13792 "Should only try to remove roots which are still added!");
13793 if (index == NotFound || !sInstance) {
13794 return;
13796 sInstance->mRoots.RemoveElementAt(index);
13797 if (sInstance->mRoots.IsEmpty()) {
13798 delete sInstance;
13799 sInstance = nullptr;
13803 /* static */
13804 bool FullscreenRoots::IsEmpty() { return !sInstance; }
13806 // Any fullscreen change waiting for the widget to finish transition
13807 // is queued here. This is declared static instead of a member of
13808 // Document because in the majority of time, there would be at most
13809 // one document requesting or exiting fullscreen. We shouldn't waste
13810 // the space to hold for it in every document.
13811 class PendingFullscreenChangeList {
13812 public:
13813 PendingFullscreenChangeList() = delete;
13815 template <typename T>
13816 static void Add(UniquePtr<T> aChange) {
13817 sList.insertBack(aChange.release());
13820 static const FullscreenChange* GetLast() { return sList.getLast(); }
13822 enum IteratorOption {
13823 // When we are committing fullscreen changes or preparing for
13824 // that, we generally want to iterate all requests in the same
13825 // window with eDocumentsWithSameRoot option.
13826 eDocumentsWithSameRoot,
13827 // If we are removing a document from the tree, we would only
13828 // want to remove the requests from the given document and its
13829 // descendants. For that case, use eInclusiveDescendants.
13830 eInclusiveDescendants
13833 template <typename T>
13834 class Iterator {
13835 public:
13836 explicit Iterator(Document* aDoc, IteratorOption aOption)
13837 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
13838 if (mCurrent) {
13839 if (aDoc->GetBrowsingContext()) {
13840 mRootBCForIteration = aDoc->GetBrowsingContext();
13841 if (aOption == eDocumentsWithSameRoot) {
13842 RefPtr<BrowsingContext> bc =
13843 GetParentIgnoreChromeBoundary(mRootBCForIteration);
13844 while (bc) {
13845 mRootBCForIteration = bc;
13846 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
13850 SkipToNextMatch();
13854 UniquePtr<T> TakeAndNext() {
13855 auto thisChange = TakeAndNextInternal();
13856 SkipToNextMatch();
13857 return thisChange;
13859 bool AtEnd() const { return mCurrent == nullptr; }
13861 private:
13862 already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
13863 BrowsingContext* aBC) {
13864 // Chrome BrowsingContexts are only available in the parent process, so if
13865 // we're in a content process, we only worry about the context tree.
13866 if (XRE_IsParentProcess()) {
13867 return aBC->Canonical()->GetParentCrossChromeBoundary();
13869 return do_AddRef(aBC->GetParent());
13872 UniquePtr<T> TakeAndNextInternal() {
13873 FullscreenChange* thisChange = mCurrent;
13874 MOZ_ASSERT(thisChange->Type() == T::kType);
13875 mCurrent = mCurrent->removeAndGetNext();
13876 return WrapUnique(static_cast<T*>(thisChange));
13878 void SkipToNextMatch() {
13879 while (mCurrent) {
13880 if (mCurrent->Type() == T::kType) {
13881 RefPtr<BrowsingContext> bc =
13882 mCurrent->Document()->GetBrowsingContext();
13883 if (!bc) {
13884 // Always automatically drop fullscreen changes which are
13885 // from a document detached from the doc shell.
13886 UniquePtr<T> change = TakeAndNextInternal();
13887 change->MayRejectPromise("Document is not active");
13888 continue;
13890 while (bc && bc != mRootBCForIteration) {
13891 bc = GetParentIgnoreChromeBoundary(bc);
13893 if (bc) {
13894 break;
13897 // The current one either don't have matched type, or isn't
13898 // inside the given subtree, so skip this item.
13899 mCurrent = mCurrent->getNext();
13903 FullscreenChange* mCurrent;
13904 RefPtr<BrowsingContext> mRootBCForIteration;
13907 private:
13908 static LinkedList<FullscreenChange> sList;
13911 /* static */
13912 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
13914 Document* Document::GetFullscreenRoot() {
13915 nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
13916 return root;
13919 size_t Document::CountFullscreenElements() const {
13920 size_t count = 0;
13921 for (const nsWeakPtr& ptr : mTopLayer) {
13922 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
13923 if (elem->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
13924 count++;
13928 return count;
13931 void Document::SetFullscreenRoot(Document* aRoot) {
13932 mFullscreenRoot = do_GetWeakReference(aRoot);
13935 void Document::TryCancelDialog() {
13936 // Check if the document is blocked by modal dialog
13937 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
13938 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
13939 if (HTMLDialogElement* dialog =
13940 HTMLDialogElement::FromNodeOrNull(element)) {
13941 dialog->QueueCancelDialog();
13942 break;
13947 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
13948 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
13949 RefPtr<Promise> promise = exit->GetPromise();
13950 RestorePreviousFullscreenState(std::move(exit));
13951 return promise.forget();
13954 static void AskWindowToExitFullscreen(Document* aDoc) {
13955 if (XRE_GetProcessType() == GeckoProcessType_Content) {
13956 nsContentUtils::DispatchEventOnlyToChrome(
13957 aDoc, ToSupports(aDoc), u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
13958 Cancelable::eNo, /* DefaultAction */ nullptr);
13959 } else {
13960 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
13961 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
13966 class nsCallExitFullscreen : public Runnable {
13967 public:
13968 explicit nsCallExitFullscreen(Document* aDoc)
13969 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
13971 NS_IMETHOD Run() final {
13972 if (!mDoc) {
13973 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
13974 } else {
13975 AskWindowToExitFullscreen(mDoc);
13977 return NS_OK;
13980 private:
13981 nsCOMPtr<Document> mDoc;
13984 /* static */
13985 void Document::AsyncExitFullscreen(Document* aDoc) {
13986 MOZ_RELEASE_ASSERT(NS_IsMainThread());
13987 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
13988 if (aDoc) {
13989 aDoc->Dispatch(TaskCategory::Other, exit.forget());
13990 } else {
13991 NS_DispatchToCurrentThread(exit.forget());
13995 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
13996 uint32_t count = 0;
13997 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
13998 auto subDoc = [&count](Document& aSubDoc) {
13999 if (aSubDoc.GetUnretargetedFullScreenElement()) {
14000 count++;
14002 return CallState::Continue;
14004 aDoc.EnumerateSubDocuments(subDoc);
14005 return count;
14008 bool Document::IsFullscreenLeaf() {
14009 // A fullscreen leaf document is fullscreen, and has no fullscreen
14010 // subdocuments.
14011 if (!GetUnretargetedFullScreenElement()) {
14012 return false;
14014 return CountFullscreenSubDocuments(*this) == 0;
14017 static Document* GetFullscreenLeaf(Document& aDoc) {
14018 if (aDoc.IsFullscreenLeaf()) {
14019 return &aDoc;
14021 if (!aDoc.GetUnretargetedFullScreenElement()) {
14022 return nullptr;
14024 Document* leaf = nullptr;
14025 auto recurse = [&leaf](Document& aSubDoc) {
14026 leaf = GetFullscreenLeaf(aSubDoc);
14027 return leaf ? CallState::Stop : CallState::Continue;
14029 aDoc.EnumerateSubDocuments(recurse);
14030 return leaf;
14033 static Document* GetFullscreenLeaf(Document* aDoc) {
14034 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14035 return leaf;
14037 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14038 // below the fullscreen doc. Start the search from the root.
14039 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14040 return GetFullscreenLeaf(*root);
14043 static CallState ResetFullscreen(Document& aDocument) {
14044 if (Element* fsElement = aDocument.GetUnretargetedFullScreenElement()) {
14045 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
14046 "Should have at most 1 fullscreen subdocument.");
14047 aDocument.CleanupFullscreenState();
14048 NS_ASSERTION(!aDocument.GetUnretargetedFullScreenElement(),
14049 "Should reset fullscreen");
14050 DispatchFullscreenChange(aDocument, fsElement);
14051 aDocument.EnumerateSubDocuments(ResetFullscreen);
14053 return CallState::Continue;
14056 // Since Document::ExitFullscreenInDocTree() could be called from
14057 // Element::UnbindFromTree() where it is not safe to synchronously run
14058 // script. This runnable is the script part of that function.
14059 class ExitFullscreenScriptRunnable : public Runnable {
14060 public:
14061 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
14062 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
14063 mRoot(aRoot),
14064 mLeaf(aLeaf) {}
14066 NS_IMETHOD Run() override {
14067 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
14068 // document since we want this event to follow the same path that
14069 // MozDOMFullscreen:Entered was dispatched.
14070 nsContentUtils::DispatchEventOnlyToChrome(
14071 mLeaf, ToSupports(mLeaf), u"MozDOMFullscreen:Exited"_ns,
14072 CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
14073 // Ensure the window exits fullscreen.
14074 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
14075 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
14076 false);
14078 return NS_OK;
14081 private:
14082 nsCOMPtr<Document> mRoot;
14083 nsCOMPtr<Document> mLeaf;
14086 /* static */
14087 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
14088 MOZ_ASSERT(aMaybeNotARootDoc);
14090 // Unlock the pointer
14091 PointerLockManager::Unlock();
14093 // Resolve all promises which waiting for exit fullscreen.
14094 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
14095 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14096 while (!iter.AtEnd()) {
14097 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
14098 exit->MayResolvePromise();
14101 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
14102 if (!root || !root->GetUnretargetedFullScreenElement()) {
14103 // If a document was detached before exiting from fullscreen, it is
14104 // possible that the root had left fullscreen state. In this case,
14105 // we would not get anything from the ResetFullscreen() call. Root's
14106 // not being a fullscreen doc also means the widget should have
14107 // exited fullscreen state. It means even if we do not return here,
14108 // we would actually do nothing below except crashing ourselves via
14109 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
14110 // document.
14111 return;
14114 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
14115 // See ExitFullscreenScriptRunnable::Run for details. We have to
14116 // record it here because we don't have such information after we
14117 // reset the fullscreen state below.
14118 Document* fullscreenLeaf = GetFullscreenLeaf(root);
14120 // Walk the tree of fullscreen documents, and reset their fullscreen state.
14121 ResetFullscreen(*root);
14123 NS_ASSERTION(!root->GetUnretargetedFullScreenElement(),
14124 "Fullscreen root should no longer be a fullscreen doc...");
14126 // Move the top-level window out of fullscreen mode.
14127 FullscreenRoots::Remove(root);
14129 nsContentUtils::AddScriptRunner(
14130 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
14133 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
14134 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14135 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
14136 CanBubble::eYes, ChromeOnlyDispatch::eYes);
14137 asyncDispatcher->PostDOMEvent();
14140 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
14141 NS_ASSERTION(
14142 !GetUnretargetedFullScreenElement() || !FullscreenRoots::IsEmpty(),
14143 "Should have at least 1 fullscreen root when fullscreen!");
14145 if (!GetWindow()) {
14146 aExit->MayRejectPromise("No active window");
14147 return;
14149 if (!GetUnretargetedFullScreenElement() || FullscreenRoots::IsEmpty()) {
14150 aExit->MayRejectPromise("Not in fullscreen mode");
14151 return;
14154 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
14155 AutoTArray<Element*, 8> exitElements;
14157 Document* doc = fullScreenDoc;
14158 // Collect all subdocuments.
14159 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
14160 Element* fsElement = doc->GetUnretargetedFullScreenElement();
14161 MOZ_ASSERT(fsElement,
14162 "Parent document of "
14163 "a fullscreen document without fullscreen element?");
14164 exitElements.AppendElement(fsElement);
14166 MOZ_ASSERT(doc == this, "Must have reached this doc");
14167 // Collect all ancestor documents which we are going to change.
14168 for (; doc; doc = doc->GetInProcessParentDocument()) {
14169 Element* fsElement = doc->GetUnretargetedFullScreenElement();
14170 MOZ_ASSERT(fsElement,
14171 "Ancestor of fullscreen document must also be in fullscreen");
14172 if (doc != this) {
14173 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
14174 if (iframe->FullscreenFlag()) {
14175 // If this is an iframe, and it explicitly requested
14176 // fullscreen, don't rollback it automatically.
14177 break;
14181 exitElements.AppendElement(fsElement);
14182 if (doc->CountFullscreenElements() > 1) {
14183 break;
14187 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
14188 size_t fullscreenCount = lastDoc->CountFullscreenElements();
14189 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
14190 // If we are fully exiting fullscreen, don't touch anything here,
14191 // just wait for the window to get out from fullscreen first.
14192 PendingFullscreenChangeList::Add(std::move(aExit));
14193 AskWindowToExitFullscreen(this);
14194 return;
14197 // If fullscreen mode is updated the pointer should be unlocked
14198 PointerLockManager::Unlock();
14199 // All documents listed in the array except the last one are going to
14200 // completely exit from the fullscreen state.
14201 for (auto i : IntegerRange(exitElements.Length() - 1)) {
14202 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
14204 // The last document will either rollback one fullscreen element, or
14205 // completely exit from the fullscreen state as well.
14206 Document* newFullscreenDoc;
14207 if (fullscreenCount > 1) {
14208 lastDoc->UnsetFullscreenElement();
14209 newFullscreenDoc = lastDoc;
14210 } else {
14211 lastDoc->CleanupFullscreenState();
14212 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
14214 // Dispatch the fullscreenchange event to all document listed. Note
14215 // that the loop order is reversed so that events are dispatched in
14216 // the tree order as indicated in the spec.
14217 for (Element* e : Reversed(exitElements)) {
14218 DispatchFullscreenChange(*e->OwnerDoc(), e);
14220 aExit->MayResolvePromise();
14222 MOZ_ASSERT(newFullscreenDoc,
14223 "If we were going to exit from fullscreen on "
14224 "all documents in this doctree, we should've asked the window to "
14225 "exit first instead of reaching here.");
14226 if (fullScreenDoc != newFullscreenDoc &&
14227 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
14228 // We've popped so enough off the stack that we've rolled back to
14229 // a fullscreen element in a parent document. If this document is
14230 // cross origin, dispatch an event to chrome so it knows to show
14231 // the warning UI.
14232 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
14236 class nsCallRequestFullscreen : public Runnable {
14237 public:
14238 explicit nsCallRequestFullscreen(UniquePtr<FullscreenRequest> aRequest)
14239 : mozilla::Runnable("nsCallRequestFullscreen"),
14240 mRequest(std::move(aRequest)) {}
14242 NS_IMETHOD Run() override {
14243 Document* doc = mRequest->Document();
14244 doc->RequestFullscreen(std::move(mRequest));
14245 return NS_OK;
14248 UniquePtr<FullscreenRequest> mRequest;
14251 void Document::AsyncRequestFullscreen(UniquePtr<FullscreenRequest> aRequest) {
14252 // Request fullscreen asynchronously.
14253 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14254 nsCOMPtr<nsIRunnable> event =
14255 new nsCallRequestFullscreen(std::move(aRequest));
14256 Dispatch(TaskCategory::Other, event.forget());
14259 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
14260 if (nsPresContext* presContext = aDoc->GetPresContext()) {
14261 presContext->UpdateViewportScrollStylesOverride();
14265 static void NotifyFullScreenChangedForMediaElement(Element* aElement,
14266 bool aIsInFullScreen) {
14267 // When a media element enters the fullscreen, we would like to notify that
14268 // to the media controller in order to update its status.
14269 if (!aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
14270 return;
14272 HTMLMediaElement* mediaElem = HTMLMediaElement::FromNodeOrNull(aElement);
14273 mediaElem->NotifyFullScreenChanged();
14276 static void ClearFullscreenStateOnElement(Element* aElement) {
14277 // Remove styles from existing top element.
14278 EventStateManager::SetFullscreenState(aElement, false);
14279 NotifyFullScreenChangedForMediaElement(aElement, false);
14280 // Reset iframe fullscreen flag.
14281 if (aElement->IsHTMLElement(nsGkAtoms::iframe)) {
14282 static_cast<HTMLIFrameElement*>(aElement)->SetFullscreenFlag(false);
14286 void Document::CleanupFullscreenState() {
14287 // Iterate the top layer and clear the fullscreen states.
14288 // Since we also need to clear the fullscreen-ancestor state, and
14289 // currently fullscreen elements can only be placed in hierarchy
14290 // order in the stack, reversely iterating the stack could be more
14291 // efficient. NOTE that fullscreen-ancestor state would be removed
14292 // in bug 1199529, and the elements may not in hierarchy order
14293 // after bug 1195213.
14294 mTopLayer.RemoveElementsBy([&](const nsWeakPtr& weakPtr) {
14295 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14296 if (!element || !element->IsInComposedDoc() ||
14297 element->OwnerDoc() != this) {
14298 return true;
14301 if (element->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
14302 ClearFullscreenStateOnElement(element);
14303 return true;
14305 return false;
14308 mFullscreenRoot = nullptr;
14310 // Restore the zoom level that was in place prior to entering fullscreen.
14311 if (PresShell* presShell = GetPresShell()) {
14312 if (presShell->GetMobileViewportManager()) {
14313 presShell->SetResolutionAndScaleTo(
14314 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
14318 UpdateViewportScrollbarOverrideForFullscreen(this);
14321 void Document::UnsetFullscreenElement() {
14322 Element* removedElement = TopLayerPop([](Element* element) -> bool {
14323 return element->State().HasState(NS_EVENT_STATE_FULLSCREEN);
14326 MOZ_ASSERT(removedElement->State().HasState(NS_EVENT_STATE_FULLSCREEN));
14327 ClearFullscreenStateOnElement(removedElement);
14328 UpdateViewportScrollbarOverrideForFullscreen(this);
14331 void Document::SetFullscreenElement(Element* aElement) {
14332 TopLayerPush(aElement);
14333 EventStateManager::SetFullscreenState(aElement, true);
14334 NotifyFullScreenChangedForMediaElement(aElement, true);
14335 UpdateViewportScrollbarOverrideForFullscreen(this);
14338 void Document::TopLayerPush(Element* aElement) {
14339 NS_ASSERTION(aElement, "Must pass non-null to TopLayerPush()");
14340 auto predictFunc = [&aElement](Element* element) {
14341 return element == aElement;
14343 TopLayerPop(predictFunc);
14345 mTopLayer.AppendElement(do_GetWeakReference(aElement));
14346 NS_ASSERTION(GetTopLayerTop() == aElement, "Should match");
14349 void Document::SetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
14350 Element* root = GetRootElement();
14351 MOZ_RELEASE_ASSERT(root, "dialog in document without root?");
14353 // Add inert to the root element so that the inertness is
14354 // applied to the entire document. Since the modal dialog
14355 // also inherits the inertness, adding
14356 // NS_EVENT_STATE_TOPMOST_MODAL_DIALOG to remove the inertness
14357 // explicitly.
14358 root->AddStates(NS_EVENT_STATE_MOZINERT);
14359 aDialogElement.AddStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
14361 // It's possible that there's another modal dialog has opened
14362 // previously which doesn't have the inertness (because we've
14363 // removed the inertness explicitly). Since a
14364 // new modal dialog is opened, we need to grant the inertness
14365 // to the previous one.
14366 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14367 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14368 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14369 if (dialog != &aDialogElement) {
14370 dialog->RemoveStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
14371 // It's ok to exit the loop as only one modal dialog should
14372 // have the state
14373 break;
14379 void Document::UnsetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
14380 aDialogElement.RemoveStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
14382 // The document could still be blocked by another modal dialog.
14383 // We need to remove the inertness from this modal dialog.
14384 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14385 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14386 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14387 if (dialog != &aDialogElement) {
14388 dialog->AddStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
14389 // Return here because we want to keep the inertness for the
14390 // root element as the document is still blocked by a modal
14391 // dialog
14392 return;
14397 Element* root = GetRootElement();
14398 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14399 root->RemoveStates(NS_EVENT_STATE_MOZINERT);
14403 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicateFunc) {
14404 if (mTopLayer.IsEmpty()) {
14405 return nullptr;
14408 // Remove the topmost element that qualifies aPredicate; This
14409 // is required is because the top layer contains not only
14410 // fullscreen elements, but also dialog elements.
14411 Element* removedElement = nullptr;
14412 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14413 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14414 if (element && aPredicateFunc(element)) {
14415 removedElement = element;
14416 mTopLayer.RemoveElementAt(i);
14417 break;
14421 // Pop from the stack null elements (references to elements which have
14422 // been GC'd since they were added to the stack) and elements which are
14423 // no longer in this document.
14425 // FIXME(emilio): If this loop does something, it'd violate the assertions
14426 // from GetTopLayerTop()... What gives?
14427 while (!mTopLayer.IsEmpty()) {
14428 Element* element = GetTopLayerTop();
14429 if (!element || element->GetComposedDoc() != this) {
14430 mTopLayer.RemoveLastElement();
14431 } else {
14432 // The top element of the stack is now an in-doc element. Return here.
14433 break;
14437 return removedElement;
14440 Element* Document::GetTopLayerTop() {
14441 if (mTopLayer.IsEmpty()) {
14442 return nullptr;
14444 uint32_t last = mTopLayer.Length() - 1;
14445 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
14446 NS_ASSERTION(element, "Should have a top layer element!");
14447 NS_ASSERTION(element->IsInComposedDoc(),
14448 "Top layer element should be in doc");
14449 NS_ASSERTION(element->OwnerDoc() == this,
14450 "Top layer element should be in this doc");
14451 return element;
14454 Element* Document::GetUnretargetedFullScreenElement() const {
14455 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14456 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14457 // Per spec, the fullscreen element is the topmost element in the document’s
14458 // top layer whose fullscreen flag is set, if any, and null otherwise.
14459 if (element && element->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
14460 return element;
14463 return nullptr;
14466 nsTArray<Element*> Document::GetTopLayer() const {
14467 nsTArray<Element*> elements;
14468 for (const nsWeakPtr& ptr : mTopLayer) {
14469 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14470 elements.AppendElement(elem);
14473 return elements;
14476 // Returns true if aDoc is in the focused tab in the active window.
14477 bool IsInActiveTab(Document* aDoc) {
14478 BrowsingContext* bc = aDoc->GetBrowsingContext();
14479 if (!bc) {
14480 return false;
14483 if (!bc->IsActive()) {
14484 return false;
14487 nsFocusManager* fm = nsFocusManager::GetFocusManager();
14488 if (!fm) {
14489 return false;
14492 if (XRE_IsParentProcess()) {
14493 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
14494 // by retaining the old code path for the parent process.
14495 nsIDocShell* docshell = aDoc->GetDocShell();
14496 if (!docshell) {
14497 return false;
14499 nsCOMPtr<nsIDocShellTreeItem> rootItem;
14500 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
14501 if (!rootItem) {
14502 return false;
14504 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
14505 if (!rootWin) {
14506 return false;
14509 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
14510 activeWindow = fm->GetActiveWindow();
14511 if (!activeWindow) {
14512 return false;
14515 return activeWindow == rootWin;
14518 return fm->GetActiveBrowsingContext() == bc->Top();
14521 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
14522 // Ensure the frame element is the fullscreen element in this document.
14523 // If the frame element is already the fullscreen element in this document,
14524 // this has no effect.
14525 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
14526 RequestFullscreen(std::move(request), XRE_IsContentProcess());
14529 void Document::RemoteFrameFullscreenReverted() {
14530 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
14531 RestorePreviousFullscreenState(std::move(exit));
14534 static bool HasFullscreenSubDocument(Document& aDoc) {
14535 uint32_t count = CountFullscreenSubDocuments(aDoc);
14536 NS_ASSERTION(count <= 1,
14537 "Fullscreen docs should have at most 1 fullscreen child!");
14538 return count >= 1;
14541 // Returns nullptr if a request for Fullscreen API is currently enabled
14542 // in the given document. Returns a static string indicates the reason
14543 // why it is not enabled otherwise.
14544 const char* Document::GetFullscreenError(CallerType aCallerType) {
14545 if (!StaticPrefs::full_screen_api_enabled()) {
14546 return "FullscreenDeniedDisabled";
14549 if (aCallerType == CallerType::System) {
14550 // Chrome code can always use the fullscreen API, provided it's not
14551 // explicitly disabled.
14552 return nullptr;
14555 if (!IsVisible()) {
14556 return "FullscreenDeniedHidden";
14559 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
14560 return "FullscreenDeniedFeaturePolicy";
14563 // Ensure that all containing elements are <iframe> and have allowfullscreen
14564 // attribute set.
14565 BrowsingContext* bc = GetBrowsingContext();
14566 if (!bc || !bc->FullscreenAllowed()) {
14567 return "FullscreenDeniedContainerNotAllowed";
14570 return nullptr;
14573 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
14574 Element* elem = aRequest.Element();
14575 // Strictly speaking, this isn't part of the fullscreen element ready
14576 // check in the spec, but per steps in the spec, when an element which
14577 // is already the fullscreen element requests fullscreen, nothing
14578 // should change and no event should be dispatched, but we still need
14579 // to resolve the returned promise.
14580 Element* fullscreenElement = GetUnretargetedFullScreenElement();
14581 if (elem == fullscreenElement) {
14582 aRequest.MayResolvePromise();
14583 return false;
14585 if (!elem->IsInComposedDoc()) {
14586 aRequest.Reject("FullscreenDeniedNotInDocument");
14587 return false;
14589 if (elem->OwnerDoc() != this) {
14590 aRequest.Reject("FullscreenDeniedMovedDocument");
14591 return false;
14593 if (!GetWindow()) {
14594 aRequest.Reject("FullscreenDeniedLostWindow");
14595 return false;
14597 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
14598 aRequest.Reject(msg);
14599 return false;
14601 if (HasFullscreenSubDocument(*this)) {
14602 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
14603 return false;
14605 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
14606 aRequest.Reject("FullscreenDeniedHTMLDialog");
14607 return false;
14609 // XXXsmaug Note, we don't follow the latest fullscreen spec here.
14610 // This whole check could be probably removed.
14611 if (fullscreenElement && !nsContentUtils::ContentIsHostIncludingDescendantOf(
14612 elem, fullscreenElement)) {
14613 // If this document is fullscreen, only grant fullscreen requests from
14614 // a descendant of the current fullscreen element.
14615 aRequest.Reject("FullscreenDeniedNotDescendant");
14616 return false;
14618 if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) {
14619 aRequest.Reject("FullscreenDeniedNotFocusedTab");
14620 return false;
14622 // Deny requests when a windowed plugin is focused.
14623 nsFocusManager* fm = nsFocusManager::GetFocusManager();
14624 if (!fm) {
14625 NS_WARNING("Failed to retrieve focus manager in fullscreen request.");
14626 aRequest.MayRejectPromise("An unexpected error occurred");
14627 return false;
14629 if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(
14630 fm->GetFocusedElement())) {
14631 aRequest.Reject("FullscreenDeniedFocusedPlugin");
14632 return false;
14634 return true;
14637 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
14638 MOZ_ASSERT(XRE_IsParentProcess());
14639 nsIDocShell* docShell = aDoc->GetDocShell();
14640 if (!docShell) {
14641 return nullptr;
14643 nsCOMPtr<nsIDocShellTreeItem> rootItem;
14644 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
14645 return rootItem ? rootItem->GetWindow() : nullptr;
14648 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
14649 nsPIDOMWindowOuter* aRootWin) {
14650 MOZ_ASSERT(XRE_IsParentProcess());
14651 // If we are in the chrome process, and the window has not been in
14652 // fullscreen, we certainly need to make that fullscreen first.
14653 if (!aRootWin->GetFullScreen()) {
14654 return false;
14656 // The iterator not being at end indicates there is still some
14657 // pending fullscreen request relates to this document. We have to
14658 // push the request to the pending queue so requests are handled
14659 // in the correct order.
14660 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
14661 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14662 if (!iter.AtEnd()) {
14663 return false;
14665 // We have to apply the fullscreen state directly in this case,
14666 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
14667 // if it is already in fullscreen. If we do not apply the state but
14668 // instead add it to the queue and wait for the window as normal,
14669 // we would get stuck.
14670 return true;
14673 static bool CheckFullscreenAllowedElementType(const Element* elem) {
14674 // Per spec only HTML, <svg>, and <math> should be allowed, but
14675 // we also need to allow XUL elements right now.
14676 return elem->IsHTMLElement() || elem->IsXULElement() ||
14677 elem->IsSVGElement(nsGkAtoms::svg) ||
14678 elem->IsMathMLElement(nsGkAtoms::math);
14681 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
14682 bool applyFullScreenDirectly) {
14683 if (XRE_IsContentProcess()) {
14684 RequestFullscreenInContentProcess(std::move(aRequest),
14685 applyFullScreenDirectly);
14686 } else {
14687 RequestFullscreenInParentProcess(std::move(aRequest),
14688 applyFullScreenDirectly);
14692 void Document::RequestFullscreenInContentProcess(
14693 UniquePtr<FullscreenRequest> aRequest, bool applyFullScreenDirectly) {
14694 MOZ_ASSERT(XRE_IsContentProcess());
14696 // If we are in the content process, we can apply the fullscreen
14697 // state directly only if we have been in DOM fullscreen, because
14698 // otherwise we always need to notify the chrome.
14699 if (applyFullScreenDirectly ||
14700 !!nsContentUtils::GetInProcessSubtreeRootDocument(this)
14701 ->GetUnretargetedFullScreenElement()) {
14702 ApplyFullscreen(std::move(aRequest));
14703 return;
14706 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
14707 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
14708 return;
14711 // We don't need to check element ready before this point, because
14712 // if we called ApplyFullscreen, it would check that for us.
14713 if (!FullscreenElementReadyCheck(*aRequest)) {
14714 return;
14717 PendingFullscreenChangeList::Add(std::move(aRequest));
14718 // If we are not the top level process, dispatch an event to make
14719 // our parent process go fullscreen first.
14720 nsContentUtils::DispatchEventOnlyToChrome(
14721 this, ToSupports(this), u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
14722 Cancelable::eNo, /* DefaultAction */ nullptr);
14725 void Document::RequestFullscreenInParentProcess(
14726 UniquePtr<FullscreenRequest> aRequest, bool applyFullScreenDirectly) {
14727 MOZ_ASSERT(XRE_IsParentProcess());
14728 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
14729 if (!rootWin) {
14730 aRequest->MayRejectPromise("No active window");
14731 return;
14734 if (applyFullScreenDirectly || ShouldApplyFullscreenDirectly(this, rootWin)) {
14735 ApplyFullscreen(std::move(aRequest));
14736 return;
14739 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
14740 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
14741 return;
14744 // We don't need to check element ready before this point, because
14745 // if we called ApplyFullscreen, it would check that for us.
14746 if (!FullscreenElementReadyCheck(*aRequest)) {
14747 return;
14750 PendingFullscreenChangeList::Add(std::move(aRequest));
14751 // Make the window fullscreen.
14752 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
14755 /* static */
14756 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
14757 bool handled = false;
14758 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
14759 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14760 while (!iter.AtEnd()) {
14761 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
14762 Document* doc = request->Document();
14763 if (doc->ApplyFullscreen(std::move(request))) {
14764 handled = true;
14767 return handled;
14770 static void ClearPendingFullscreenRequests(Document* aDoc) {
14771 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
14772 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
14773 while (!iter.AtEnd()) {
14774 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
14775 request->MayRejectPromise("Fullscreen request aborted");
14779 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
14780 if (!FullscreenElementReadyCheck(*aRequest)) {
14781 return false;
14784 // Stash a reference to any existing fullscreen doc, we'll use this later
14785 // to detect if the origin which is fullscreen has changed.
14786 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
14788 // Stores a list of documents which we must dispatch "fullscreenchange"
14789 // too. We're required by the spec to dispatch the events in root-to-leaf
14790 // order, but we traverse the doctree in a leaf-to-root order, so we save
14791 // references to the documents we must dispatch to so that we get the order
14792 // as specified.
14793 AutoTArray<Document*, 8> changed;
14795 // Remember the root document, so that if a fullscreen document is hidden
14796 // we can reset fullscreen state in the remaining visible fullscreen
14797 // documents.
14798 Document* fullScreenRootDoc =
14799 nsContentUtils::GetInProcessSubtreeRootDocument(this);
14801 // If a document is already in fullscreen, then unlock the mouse pointer
14802 // before setting a new document to fullscreen
14803 PointerLockManager::Unlock();
14805 // Set the fullscreen element. This sets the fullscreen style on the
14806 // element, and the fullscreen-ancestor styles on ancestors of the element
14807 // in this document.
14808 Element* elem = aRequest->Element();
14809 SetFullscreenElement(elem);
14810 // Set the iframe fullscreen flag.
14811 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
14812 iframe->SetFullscreenFlag(true);
14814 changed.AppendElement(this);
14816 // Propagate up the document hierarchy, setting the fullscreen element as
14817 // the element's container in ancestor documents. This also sets the
14818 // appropriate css styles as well. Note we don't propagate down the
14819 // document hierarchy, the fullscreen element (or its container) is not
14820 // visible there. Stop when we reach the root document.
14821 Document* child = this;
14822 while (true) {
14823 child->SetFullscreenRoot(fullScreenRootDoc);
14825 // When entering fullscreen, reset the RCD's resolution to the intrinsic
14826 // resolution, otherwise the fullscreen content could be sized larger than
14827 // the screen (since fullscreen is implemented using position:fixed and
14828 // fixed elements are sized to the layout viewport).
14829 // This also ensures that things like video controls aren't zoomed in
14830 // when in fullscreen mode.
14831 if (PresShell* presShell = child->GetPresShell()) {
14832 if (RefPtr<MobileViewportManager> manager =
14833 presShell->GetMobileViewportManager()) {
14834 // Save the previous resolution so it can be restored.
14835 child->mSavedResolution = presShell->GetResolution();
14836 presShell->SetResolutionAndScaleTo(
14837 manager->ComputeIntrinsicResolution(),
14838 ResolutionChangeOrigin::MainThreadRestore);
14842 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
14843 "Fullscreen root should be set!");
14844 if (child == fullScreenRootDoc) {
14845 break;
14848 Element* element = child->GetEmbedderElement();
14849 if (!element) {
14850 // We've reached the root.No more changes need to be made
14851 // to the top layer stacks of documents further up the tree.
14852 break;
14855 Document* parent = child->GetInProcessParentDocument();
14856 parent->SetFullscreenElement(element);
14857 changed.AppendElement(parent);
14858 child = parent;
14861 FullscreenRoots::Add(this);
14863 // If it is the first entry of the fullscreen, trigger an event so
14864 // that the UI can response to this change, e.g. hide chrome, or
14865 // notifying parent process to enter fullscreen. Note that chrome
14866 // code may also want to listen to MozDOMFullscreen:NewOrigin event
14867 // to pop up warning UI.
14868 if (!previousFullscreenDoc) {
14869 nsContentUtils::DispatchEventOnlyToChrome(
14870 this, ToSupports(elem), u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
14871 Cancelable::eNo, /* DefaultAction */ nullptr);
14874 // The origin which is fullscreen gets changed. Trigger an event so
14875 // that the chrome knows to pop up a warning UI. Note that
14876 // previousFullscreenDoc == nullptr upon first entry, so we always
14877 // take this path on the first entry. Also note that, in a multi-
14878 // process browser, the code in content process is responsible for
14879 // sending message with the origin to its parent, and the parent
14880 // shouldn't rely on this event itself.
14881 if (aRequest->mShouldNotifyNewOrigin &&
14882 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
14883 DispatchFullscreenNewOriginEvent(this);
14886 // Dispatch "fullscreenchange" events. Note that the loop order is
14887 // reversed so that events are dispatched in the tree order as
14888 // indicated in the spec.
14889 for (Document* d : Reversed(changed)) {
14890 DispatchFullscreenChange(*d, d->GetUnretargetedFullScreenElement());
14892 aRequest->MayResolvePromise();
14893 return true;
14896 void Document::ClearOrientationPendingPromise() {
14897 mOrientationPendingPromise = nullptr;
14900 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
14901 if (mIsGoingAway) {
14902 return false;
14905 mOrientationPendingPromise = aPromise;
14906 return true;
14909 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
14910 dom::VisibilityState oldState = mVisibilityState;
14911 mVisibilityState = ComputeVisibilityState();
14912 if (oldState != mVisibilityState) {
14913 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
14914 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
14915 u"visibilitychange"_ns,
14916 CanBubble::eYes, Cancelable::eNo);
14918 EnumerateActivityObservers(NotifyActivityChanged);
14919 if (mVisibilityState == dom::VisibilityState::Visible) {
14920 MaybeActiveMediaComponents();
14925 VisibilityState Document::ComputeVisibilityState() const {
14926 // We have to check a few pieces of information here:
14927 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
14928 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
14929 // want to use GetWindow here because it does weird groveling for windows
14930 // in some cases.
14931 // 3) Is our outer window background? If so, we're hidden.
14932 // Otherwise, we're visible.
14933 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
14934 mWindow->GetOuterWindow()->IsBackground()) {
14935 return dom::VisibilityState::Hidden;
14938 return dom::VisibilityState::Visible;
14941 void Document::PostVisibilityUpdateEvent() {
14942 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
14943 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
14944 DispatchVisibilityChange::Yes);
14945 Dispatch(TaskCategory::Other, event.forget());
14948 void Document::MaybeActiveMediaComponents() {
14949 if (!mWindow) {
14950 return;
14953 GetWindow()->MaybeActiveMediaComponents();
14956 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
14957 nsINode::AddSizeOfExcludingThis(aWindowSizes,
14958 &aWindowSizes.mDOMSizes.mDOMOtherSize);
14960 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
14961 AddSizeOfNodeTree(*kid, aWindowSizes);
14964 // IMPORTANT: for our ComputedValues measurements, we want to measure
14965 // ComputedValues accessible from DOM elements before ComputedValues not
14966 // accessible from DOM elements (i.e. accessible only from the frame tree).
14968 // Therefore, the measurement of the Document superclass must happen after
14969 // the measurement of DOM nodes (above), because Document contains the
14970 // PresShell, which contains the frame tree.
14971 if (mPresShell) {
14972 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
14975 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
14977 aWindowSizes.mPropertyTablesSize +=
14978 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
14980 if (EventListenerManager* elm = GetExistingListenerManager()) {
14981 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
14984 if (mNodeInfoManager) {
14985 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
14988 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
14989 mDOMMediaQueryLists.sizeOfExcludingThis(
14990 aWindowSizes.mState.mMallocSizeOf);
14992 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
14993 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
14994 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
14997 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
14999 for (auto& sheetArray : mAdditionalSheets) {
15000 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15002 // Lumping in the loader with the style-sheets size is not ideal,
15003 // but most of the things in there are in fact stylesheets, so it
15004 // doesn't seem worthwhile to separate it out.
15005 // This can be null if we've already been unlinked.
15006 if (mCSSLoader) {
15007 aWindowSizes.mLayoutStyleSheetsSize +=
15008 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15011 if (mResizeObserverController) {
15012 mResizeObserverController->AddSizeOfIncludingThis(aWindowSizes);
15015 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15016 mAttrStyleSheet ? mAttrStyleSheet->DOMSizeOfIncludingThis(
15017 aWindowSizes.mState.mMallocSizeOf)
15018 : 0;
15020 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15021 mStyledLinks.ShallowSizeOfExcludingThis(
15022 aWindowSizes.mState.mMallocSizeOf);
15024 // Measurement of the following members may be added later if DMD finds it
15025 // is worthwhile:
15026 // - mMidasCommandManager
15027 // - many!
15030 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15031 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15032 aWindowSizes.mState.mMallocSizeOf(this);
15033 DocAddSizeOfExcludingThis(aWindowSizes);
15036 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15037 size_t* aNodeSize) const {
15038 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15039 // nsDocuments can only appear at the top of the DOM tree, and we use the
15040 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15041 // be called.
15042 MOZ_CRASH();
15045 /* static */
15046 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15047 size_t nodeSize = 0;
15048 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15050 // This is where we transfer the nodeSize obtained from
15051 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15052 switch (aNode.NodeType()) {
15053 case nsINode::ELEMENT_NODE:
15054 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15055 break;
15056 case nsINode::TEXT_NODE:
15057 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15058 break;
15059 case nsINode::CDATA_SECTION_NODE:
15060 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15061 break;
15062 case nsINode::COMMENT_NODE:
15063 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15064 break;
15065 default:
15066 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15067 break;
15070 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15071 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15074 if (aNode.IsContent()) {
15075 nsTArray<nsIContent*> anonKids;
15076 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15077 nsIContent::eAllChildren);
15078 for (nsIContent* anonKid : anonKids) {
15079 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15082 if (auto* element = Element::FromNode(aNode)) {
15083 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15084 AddSizeOfNodeTree(*shadow, aWindowSizes);
15089 // NOTE(emilio): If you feel smart and want to change this function to use
15090 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15091 // sane way, and kids of <content> won't point to the parent, so we'd never
15092 // find the root node where we should stop at.
15093 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15094 kid = kid->GetNextSibling()) {
15095 AddSizeOfNodeTree(*kid, aWindowSizes);
15099 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15100 ErrorResult& rv) {
15101 nsCOMPtr<nsIScriptGlobalObject> global =
15102 do_QueryInterface(aGlobal.GetAsSupports());
15103 if (!global) {
15104 rv.Throw(NS_ERROR_UNEXPECTED);
15105 return nullptr;
15108 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15109 do_QueryInterface(aGlobal.GetAsSupports());
15110 if (!prin) {
15111 rv.Throw(NS_ERROR_UNEXPECTED);
15112 return nullptr;
15115 nsCOMPtr<nsIURI> uri;
15116 NS_NewURI(getter_AddRefs(uri), "about:blank");
15117 if (!uri) {
15118 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15119 return nullptr;
15122 nsCOMPtr<Document> doc;
15123 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15124 nullptr, uri, uri, prin->GetPrincipal(),
15125 true, global, DocumentFlavorPlain);
15126 if (NS_FAILED(res)) {
15127 rv.Throw(res);
15128 return nullptr;
15131 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15133 return doc.forget();
15136 XPathExpression* Document::CreateExpression(const nsAString& aExpression,
15137 XPathNSResolver* aResolver,
15138 ErrorResult& rv) {
15139 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15142 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15143 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15146 already_AddRefed<XPathResult> Document::Evaluate(
15147 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15148 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15149 ErrorResult& rv) {
15150 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15151 aType, aResult, rv);
15154 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15155 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15156 if (!item) {
15157 return nullptr;
15159 nsCOMPtr<nsIDocShellTreeOwner> owner;
15160 item->GetTreeOwner(getter_AddRefs(owner));
15161 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15162 if (!appWin) {
15163 return nullptr;
15165 nsCOMPtr<nsIDocShell> appWinShell;
15166 appWin->GetDocShell(getter_AddRefs(appWinShell));
15167 if (!SameCOMIdentity(appWinShell, item)) {
15168 return nullptr;
15170 return appWin.forget();
15173 WindowContext* Document::GetTopLevelWindowContext() const {
15174 WindowContext* windowContext = GetWindowContext();
15175 return windowContext ? windowContext->TopWindowContext() : nullptr;
15178 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
15179 Document* parent;
15181 if (!mLoadedAsData) {
15182 parent = this;
15183 } else {
15184 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15185 if (!window) {
15186 return nullptr;
15189 parent = window->GetExtantDoc();
15190 if (!parent) {
15191 return nullptr;
15195 do {
15196 if (parent->IsTopLevelContentDocument()) {
15197 break;
15200 // If we ever have a non-content parent before we hit a toplevel content
15201 // parent, then we're never going to find one. Just bail.
15202 if (!parent->IsContentDocument()) {
15203 return nullptr;
15206 parent = parent->GetInProcessParentDocument();
15207 } while (parent);
15209 return parent;
15212 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
15213 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
15216 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
15217 MOZ_ASSERT(IsBeingUsedAsImage());
15218 MOZ_ASSERT(aReferencingDocument);
15220 if (!aReferencingDocument->mShouldReportUseCounters) {
15221 // No need to propagate use counters to a document that itself won't report
15222 // use counters.
15223 return;
15226 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15227 ("PropagateImageUseCounters from %s to %s",
15228 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
15229 nsContentUtils::TruncatedURLForDisplay(
15230 aReferencingDocument->mDocumentURI)
15231 .get()));
15233 if (aReferencingDocument->IsBeingUsedAsImage()) {
15234 NS_WARNING(
15235 "Page use counters from nested image documents may not "
15236 "propagate to the top-level document (bug 1657805)");
15239 SetCssUseCounterBits();
15240 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
15241 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
15244 bool Document::HasScriptsBlockedBySandbox() {
15245 return mSandboxFlags & SANDBOXED_SCRIPTS;
15248 // Some use-counter sanity-checking.
15249 static_assert(size_t(eUseCounter_EndCSSProperties) -
15250 size_t(eUseCounter_FirstCSSProperty) ==
15251 size_t(eCSSProperty_COUNT_with_aliases),
15252 "We should have the right amount of CSS property use counters");
15253 static_assert(size_t(eUseCounter_Count) -
15254 size_t(eUseCounter_FirstCountedUnknownProperty) ==
15255 size_t(CountedUnknownProperty::Count),
15256 "We should have the right amount of counted unknown properties"
15257 " use counters");
15258 static_assert(size_t(eUseCounter_Count) * 2 ==
15259 size_t(Telemetry::HistogramUseCounterCount),
15260 "There should be two histograms (document and page)"
15261 " for each use counter");
15263 #define ASSERT_CSS_COUNTER(id_, method_) \
15264 static_assert(size_t(eUseCounter_property_##method_) - \
15265 size_t(eUseCounter_FirstCSSProperty) == \
15266 size_t(id_), \
15267 "Order for CSS counters and CSS property id should match");
15268 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
15269 #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
15270 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
15271 #define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
15272 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
15273 #define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
15274 ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
15275 #include "mozilla/ServoCSSPropList.h"
15276 #undef CSS_PROP_ALIAS
15277 #undef CSS_PROP_SHORTHAND
15278 #undef CSS_PROP_LONGHAND
15279 #undef CSS_PROP_PUBLIC_OR_PRIVATE
15280 #undef ASSERT_CSS_COUNTER
15282 void Document::SetCssUseCounterBits() {
15283 if (StaticPrefs::layout_css_use_counters_enabled()) {
15284 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
15285 auto id = nsCSSPropertyID(i);
15286 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
15287 SetUseCounter(nsCSSProps::UseCounterFor(id));
15292 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
15293 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
15294 auto id = CountedUnknownProperty(i);
15295 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
15296 id)) {
15297 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
15303 void Document::InitUseCounters() {
15304 // We can be called more than once, e.g. when session history navigation shows
15305 // us a second time.
15306 if (mUseCountersInitialized) {
15307 return;
15309 mUseCountersInitialized = true;
15311 static_assert(Telemetry::HistogramUseCounterCount > 0);
15313 if (!ShouldIncludeInTelemetry(/* aAllowExtensionURIs = */ true)) {
15314 return;
15317 // Now we know for sure that we should report use counters from this document.
15318 mShouldReportUseCounters = true;
15320 WindowContext* top = GetWindowContextForPageUseCounters();
15321 if (!top) {
15322 // This is the case for SVG image documents. They are not displayed in a
15323 // window, but we still do want to record document use counters for them.
15325 // Page use counter propagation is handled in PropagateImageUseCounters,
15326 // so there is no need to use the cross-process machinery to send them.
15327 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15328 ("InitUseCounters for a non-displayed document [%s]",
15329 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
15330 return;
15333 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
15334 if (!wgc) {
15335 return;
15338 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15339 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
15340 " [from %s]",
15341 wgc->InnerWindowId(), top->Id(),
15342 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
15344 // Inform the parent process that we will send it page use counters later on.
15345 wgc->SendExpectPageUseCounters(top);
15346 mShouldSendPageUseCounters = true;
15349 // We keep separate counts for individual documents and top-level
15350 // pages to more accurately track how many web pages might break if
15351 // certain features were removed. Consider the case of a single
15352 // HTML document with several SVG images and/or iframes with
15353 // sub-documents of their own. If we maintained a single set of use
15354 // counters and all the sub-documents use a particular feature, then
15355 // telemetry would indicate that we would be breaking N documents if
15356 // that feature were removed. Whereas with a document/top-level
15357 // page split, we can see that N documents would be affected, but
15358 // only a single web page would be affected.
15360 // The difference between the values of these two histograms and the
15361 // related use counters below tell us how many pages did *not* use
15362 // the feature in question. For instance, if we see that a given
15363 // session has destroyed 30 content documents, but a particular use
15364 // counter shows only a count of 5, we can infer that the use
15365 // counter was *not* used in 25 of those 30 documents.
15367 // We do things this way, rather than accumulating a boolean flag
15368 // for each use counter, to avoid sending histograms for features
15369 // that don't get widely used. Doing things in this fashion means
15370 // smaller telemetry payloads and faster processing on the server
15371 // side.
15372 void Document::ReportDocumentUseCounters() {
15373 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
15374 return;
15377 mReportedDocumentUseCounters = true;
15379 // Note that a document is being destroyed. See the comment above for how
15380 // use counter histograms are interpreted relative to this measurement.
15381 // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
15382 // WindowGlobalParent::FinishAccumulatingPageUseCounters.
15383 Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
15385 // Ask all of our resource documents to report their own document use
15386 // counters.
15387 EnumerateExternalResources([](Document& aDoc) {
15388 aDoc.ReportDocumentUseCounters();
15389 return CallState::Continue;
15392 // Copy StyleUseCounters into our document use counters.
15393 SetCssUseCounterBits();
15395 // Report our per-document use counters.
15396 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15397 ("Reporting document use counters [%s]",
15398 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
15399 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
15400 auto uc = static_cast<UseCounter>(c);
15401 if (!mUseCounters[uc]) {
15402 continue;
15405 auto id = static_cast<Telemetry::HistogramID>(
15406 Telemetry::HistogramFirstUseCounter + uc * 2);
15407 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15408 (" > %s\n", Telemetry::GetHistogramName(id)));
15409 Telemetry::Accumulate(id, 1);
15412 ReportDocumentLazyLoadCounters();
15415 void Document::ReportDocumentLazyLoadCounters() {
15416 if (!mLazyLoadImageCount) {
15417 return;
15419 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_TOTAL, mLazyLoadImageCount);
15420 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_STARTED,
15421 mLazyLoadImageStarted);
15422 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_NOT_VIEWPORT,
15423 mLazyLoadImageStarted -
15424 mLazyLoadImageReachViewportLoading -
15425 mLazyLoadImageReachViewportLoaded);
15426 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_VIEWPORT_LOADING,
15427 mLazyLoadImageReachViewportLoading);
15428 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_VIEWPORT_LOADED,
15429 mLazyLoadImageReachViewportLoaded);
15432 void Document::SendPageUseCounters() {
15433 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
15434 return;
15437 // Ask all of our resource documents to send their own document use
15438 // counters to the parent process to be counted as page use counters.
15439 EnumerateExternalResources([](Document& aDoc) {
15440 aDoc.SendPageUseCounters();
15441 return CallState::Continue;
15444 // Send our use counters to the parent process to accumulate them towards the
15445 // page use counters for the top-level document.
15447 // We take our own document use counters (those in mUseCounters) and any child
15448 // document use counters (those in mChildDocumentUseCounters) that have been
15449 // explicitly propagated up to us, which includes resource documents, static
15450 // clones, and SVG images.
15451 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
15452 if (!wgc) {
15453 MOZ_ASSERT_UNREACHABLE(
15454 "SendPageUseCounters should be called while we still have access "
15455 "to our WindowContext");
15456 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15457 (" > too late to send page use counters"));
15458 return;
15461 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15462 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
15463 wgc->WindowContext()->Id(),
15464 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
15466 // Copy StyleUseCounters into our document use counters.
15467 SetCssUseCounterBits();
15469 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
15470 wgc->SendAccumulatePageUseCounters(counters);
15473 WindowContext* Document::GetWindowContextForPageUseCounters() const {
15474 if (mDisplayDocument) {
15475 // If we are a resource document, then go through it to find the
15476 // top-level document.
15477 return mDisplayDocument->GetWindowContextForPageUseCounters();
15480 if (mOriginalDocument) {
15481 // For static clones (print preview documents), contribute page use counters
15482 // towards the original document.
15483 return mOriginalDocument->GetWindowContextForPageUseCounters();
15486 WindowContext* wc = GetTopLevelWindowContext();
15487 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
15488 return nullptr;
15491 return wc;
15494 void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
15495 if (mIntersectionObservers.IsEmpty()) {
15496 return;
15499 DOMHighResTimeStamp time = 0;
15500 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
15501 if (Performance* perf = win->GetPerformance()) {
15502 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
15506 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
15507 mIntersectionObservers);
15508 for (const auto& observer : observers) {
15509 if (observer) {
15510 observer->Update(this, time);
15515 void Document::ScheduleIntersectionObserverNotification() {
15516 if (mIntersectionObservers.IsEmpty()) {
15517 return;
15519 MOZ_RELEASE_ASSERT(NS_IsMainThread());
15520 nsCOMPtr<nsIRunnable> notification =
15521 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
15522 &Document::NotifyIntersectionObservers);
15523 Dispatch(TaskCategory::Other, notification.forget());
15526 void Document::NotifyIntersectionObservers() {
15527 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
15528 mIntersectionObservers);
15529 for (const auto& observer : observers) {
15530 if (observer) {
15531 // MOZ_KnownLive because the 'observers' array guarantees to keep it
15532 // alive.
15533 MOZ_KnownLive(observer)->Notify();
15538 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserver() {
15539 if (!mLazyLoadImageObserver) {
15540 mLazyLoadImageObserver =
15541 DOMIntersectionObserver::CreateLazyLoadObserver(*this);
15543 return *mLazyLoadImageObserver;
15546 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserverViewport() {
15547 if (!mLazyLoadImageObserverViewport) {
15548 mLazyLoadImageObserverViewport =
15549 DOMIntersectionObserver::CreateLazyLoadObserverViewport(*this);
15551 return *mLazyLoadImageObserverViewport;
15554 void Document::IncLazyLoadImageReachViewport(bool aLoading) {
15555 if (aLoading) {
15556 ++mLazyLoadImageReachViewportLoading;
15557 } else {
15558 ++mLazyLoadImageReachViewportLoaded;
15562 void Document::NotifyLayerManagerRecreated() {
15563 EnumerateActivityObservers(NotifyActivityChanged);
15564 EnumerateSubDocuments([](Document& aSubDoc) {
15565 aSubDoc.NotifyLayerManagerRecreated();
15566 return CallState::Continue;
15570 XPathEvaluator* Document::XPathEvaluator() {
15571 if (!mXPathEvaluator) {
15572 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
15574 return mXPathEvaluator.get();
15577 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
15578 return mCachedEncoder.forget();
15581 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
15582 mCachedEncoder = aEncoder;
15585 void Document::SetContentTypeInternal(const nsACString& aType) {
15586 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
15587 aType.EqualsLiteral("application/xhtml+xml")) {
15588 mDefaultElementType = kNameSpaceID_XHTML;
15591 mCachedEncoder = nullptr;
15592 mContentType = aType;
15595 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
15597 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
15599 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
15600 mStateObjectContainer = scContainer;
15601 mStateObjectCached = nullptr;
15604 Document::DocumentTheme Document::GetDocumentLWTheme() {
15605 if (mDocLWTheme == Doc_Theme_Uninitialized) {
15606 mDocLWTheme = ThreadSafeGetDocumentLWTheme();
15608 return mDocLWTheme;
15611 Document::DocumentTheme Document::ThreadSafeGetDocumentLWTheme() const {
15612 if (!NodePrincipal()->IsSystemPrincipal()) {
15613 return Doc_Theme_None;
15616 if (mDocLWTheme != Doc_Theme_Uninitialized) {
15617 return mDocLWTheme;
15620 DocumentTheme theme = Doc_Theme_None; // No lightweight theme by default
15621 Element* element = GetRootElement();
15622 if (element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
15623 nsGkAtoms::_true, eCaseMatters)) {
15624 theme = Doc_Theme_Neutral;
15625 nsAutoString lwTheme;
15626 element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
15627 if (lwTheme.EqualsLiteral("dark")) {
15628 theme = Doc_Theme_Dark;
15629 } else if (lwTheme.EqualsLiteral("bright")) {
15630 theme = Doc_Theme_Bright;
15634 return theme;
15637 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
15638 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
15639 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
15640 ELEMENT_NODE);
15641 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
15643 nsCOMPtr<Element> element;
15644 DebugOnly<nsresult> rv =
15645 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
15646 mozilla::dom::NOT_FROM_PARSER);
15648 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
15649 return element.forget();
15652 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
15653 BrowsingContext* aContext) {
15654 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
15655 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
15656 if (RefPtr<Document> doc = win->GetExtantDoc()) {
15657 SuppressDocument(doc);
15658 mDocuments.AppendElement(doc);
15664 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
15665 BrowsingContextGroup* aGroup) {
15666 for (const auto& bc : aGroup->Toplevels()) {
15667 SuppressBrowsingContext(bc);
15671 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
15672 SyncOperationBehavior aSyncBehavior)
15673 : mSyncBehavior(aSyncBehavior) {
15674 mMicroTaskLevel = 0;
15675 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
15676 mMicroTaskLevel = ccjs->MicroTaskLevel();
15677 ccjs->SetMicroTaskLevel(0);
15679 if (aDoc) {
15680 mBrowsingContext = aDoc->GetBrowsingContext();
15681 if (InputTaskManager::CanSuspendInputEvent()) {
15682 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
15683 SuppressBrowsingContextGroup(bcg);
15685 } else if (mBrowsingContext) {
15686 SuppressBrowsingContext(mBrowsingContext->Top());
15688 if (mBrowsingContext &&
15689 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
15690 InputTaskManager::CanSuspendInputEvent()) {
15691 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
15696 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
15697 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
15698 win->TimeoutManager().BeginSyncOperation();
15700 aDoc->SetIsInSyncOperation(true);
15703 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
15704 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
15705 win->TimeoutManager().EndSyncOperation();
15707 aDoc->SetIsInSyncOperation(false);
15710 nsAutoSyncOperation::~nsAutoSyncOperation() {
15711 UnsuppressDocuments();
15712 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
15713 if (ccjs) {
15714 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
15716 if (mBrowsingContext &&
15717 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
15718 InputTaskManager::CanSuspendInputEvent()) {
15719 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
15723 void Document::SetIsInSyncOperation(bool aSync) {
15724 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
15725 ccjs->UpdateMicroTaskSuppressionGeneration();
15728 if (aSync) {
15729 ++mInSyncOperationCount;
15730 } else {
15731 --mInSyncOperationCount;
15735 gfxUserFontSet* Document::GetUserFontSet() {
15736 if (!mFontFaceSet) {
15737 return nullptr;
15740 return mFontFaceSet->GetUserFontSet();
15743 void Document::FlushUserFontSet() {
15744 if (!mFontFaceSetDirty) {
15745 return;
15748 mFontFaceSetDirty = false;
15750 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
15751 nsTArray<nsFontFaceRuleContainer> rules;
15752 RefPtr<PresShell> presShell = GetPresShell();
15753 if (presShell) {
15754 MOZ_ASSERT(mStyleSetFilled);
15755 mStyleSet->AppendFontFaceRules(rules);
15758 if (!mFontFaceSet && !rules.IsEmpty()) {
15759 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15760 mFontFaceSet = new FontFaceSet(window, this);
15763 bool changed = false;
15764 if (mFontFaceSet) {
15765 changed = mFontFaceSet->UpdateRules(rules);
15768 // We need to enqueue a style change reflow (for later) to
15769 // reflect that we're modifying @font-face rules. (However,
15770 // without a reflow, nothing will happen to start any downloads
15771 // that are needed.)
15772 if (changed && presShell) {
15773 if (nsPresContext* presContext = presShell->GetPresContext()) {
15774 presContext->UserFontSetUpdated();
15780 void Document::MarkUserFontSetDirty() {
15781 if (mFontFaceSetDirty) {
15782 return;
15784 mFontFaceSetDirty = true;
15785 if (PresShell* presShell = GetPresShell()) {
15786 presShell->EnsureStyleFlush();
15790 FontFaceSet* Document::Fonts() {
15791 if (!mFontFaceSet) {
15792 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15793 mFontFaceSet = new FontFaceSet(window, this);
15794 FlushUserFontSet();
15796 return mFontFaceSet;
15799 void Document::ReportHasScrollLinkedEffect() {
15800 if (mHasScrollLinkedEffect) {
15801 // We already did this once for this document, don't do it again.
15802 return;
15804 mHasScrollLinkedEffect = true;
15805 nsContentUtils::ReportToConsole(
15806 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
15807 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
15810 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
15811 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
15812 // Setting has user interction on a discarded browsing context has
15813 // no effect.
15814 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
15818 bool Document::GetSHEntryHasUserInteraction() {
15819 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
15820 return topWc->GetSHEntryHasUserInteraction();
15822 return false;
15825 void Document::SetUserHasInteracted() {
15826 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
15827 ("Document %p has been interacted by user.", this));
15829 // We maybe need to update the user-interaction permission.
15830 MaybeStoreUserInteractionAsPermission();
15832 // For purposes of reducing irrelevant session history entries on
15833 // the back button, we annotate entries with whether they had user
15834 // interaction. This is gated on its own flag on the WindowContext
15835 // (instead of mUserHasInteracted) to account for the fact that multiple
15836 // top-level SH entries can be associated with the same document.
15837 // Thus, whenever we create a new SH entry for this document,
15838 // this flag is reset.
15839 if (!GetSHEntryHasUserInteraction()) {
15840 nsIDocShell* docShell = this->GetDocShell();
15841 if (docShell) {
15842 nsCOMPtr<nsISHEntry> currentEntry;
15843 bool oshe;
15844 nsresult rv =
15845 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
15846 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
15847 currentEntry->SetHasUserInteraction(true);
15850 SetSHEntryHasUserInteraction(true);
15853 if (mUserHasInteracted) {
15854 return;
15857 mUserHasInteracted = true;
15859 if (mChannel) {
15860 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
15861 loadInfo->SetDocumentHasUserInteracted(true);
15863 // Tell the parent process about user interaction
15864 if (auto* wgc = GetWindowGlobalChild()) {
15865 wgc->SendUpdateDocumentHasUserInteracted(true);
15868 MaybeAllowStorageForOpenerAfterUserInteraction();
15871 BrowsingContext* Document::GetBrowsingContext() const {
15872 nsCOMPtr<nsIDocShell> docshell(mDocumentContainer);
15873 return docshell ? docshell->GetBrowsingContext() : nullptr;
15876 void Document::NotifyUserGestureActivation() {
15877 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
15878 bc->PreOrderWalk([&](BrowsingContext* aBC) {
15879 WindowContext* windowContext = aBC->GetCurrentWindowContext();
15880 if (!windowContext) {
15881 return;
15884 nsIDocShell* docShell = aBC->GetDocShell();
15885 if (!docShell) {
15886 return;
15889 Document* document = docShell->GetDocument();
15890 if (!document) {
15891 return;
15894 // XXXedgar we probably could just check `IsInProcess()` after fission
15895 // enable.
15896 if (NodePrincipal()->Equals(document->NodePrincipal())) {
15897 windowContext->NotifyUserGestureActivation();
15901 for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
15902 if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
15903 windowContext->NotifyUserGestureActivation();
15909 bool Document::HasBeenUserGestureActivated() {
15910 RefPtr<WindowContext> wc = GetWindowContext();
15911 return wc && wc->HasBeenUserGestureActivated();
15914 void Document::ClearUserGestureActivation() {
15915 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
15916 bc = bc->Top();
15917 bc->PreOrderWalk([&](BrowsingContext* aBC) {
15918 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
15919 windowContext->NotifyResetUserGestureActivation();
15925 bool Document::HasValidTransientUserGestureActivation() const {
15926 RefPtr<WindowContext> wc = GetWindowContext();
15927 return wc && wc->HasValidTransientUserGestureActivation();
15930 bool Document::ConsumeTransientUserGestureActivation() {
15931 RefPtr<WindowContext> wc = GetWindowContext();
15932 return wc && wc->ConsumeTransientUserGestureActivation();
15935 void Document::IncLazyLoadImageCount() {
15936 if (!mLazyLoadImageCount) {
15937 if (WindowContext* wc = GetTopLevelWindowContext()) {
15938 if (!wc->HadLazyLoadImage()) {
15939 Unused << wc->SetHadLazyLoadImage(true);
15943 ++mLazyLoadImageCount;
15946 void Document::SetDocTreeHadMedia() {
15947 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
15948 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
15949 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
15953 DocumentAutoplayPolicy Document::AutoplayPolicy() const {
15954 return AutoplayPolicy::IsAllowedToPlay(*this);
15957 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
15958 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
15959 return;
15962 // This will probably change for project fission, but currently this document
15963 // and the opener are on the same process. In the future, we should make this
15964 // part async.
15965 nsPIDOMWindowInner* inner = GetInnerWindow();
15966 if (NS_WARN_IF(!inner)) {
15967 return;
15970 uint32_t cookieBehavior = CookieJarSettings()->GetCookieBehavior();
15971 if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
15972 cookieBehavior ==
15973 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
15974 // We care about first-party tracking resources only.
15975 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
15976 return;
15978 } else {
15979 MOZ_ASSERT(net::CookieJarSettings::IsRejectThirdPartyWithExceptions(
15980 cookieBehavior));
15983 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
15984 if (NS_WARN_IF(!outer)) {
15985 return;
15988 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
15989 if (!openerBC) {
15990 // No opener.
15991 return;
15994 // We want to ensure the following check works for both fission mode and
15995 // non-fission mode:
15996 // "If the opener is not a 3rd party and if this window is not a 3rd party
15997 // with respect to the opener, we should not continue."
15999 // In non-fission mode, the opener and the opened window are in the same
16000 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16001 // In fission mode, if this window is not a 3rd party with respect to the
16002 // opener, they must be in the same process, so we can still use
16003 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16004 // party.
16005 if (openerBC->IsInProcess()) {
16006 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16007 if (NS_WARN_IF(!outerOpener)) {
16008 return;
16011 nsCOMPtr<nsPIDOMWindowInner> openerInner =
16012 outerOpener->GetCurrentInnerWindow();
16013 if (NS_WARN_IF(!openerInner)) {
16014 return;
16017 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
16018 if (NS_WARN_IF(!openerDocument)) {
16019 return;
16022 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
16023 if (NS_WARN_IF(!openerURI)) {
16024 return;
16027 // If the opener is not a 3rd party and if this window is not
16028 // a 3rd party with respect to the opener, we should not continue.
16029 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
16030 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
16031 return;
16035 // We don't care when the asynchronous work finishes here.
16036 Unused << ContentBlocking::AllowAccessFor(
16037 NodePrincipal(), openerBC,
16038 ContentBlockingNotifier::eOpenerAfterUserInteraction);
16041 namespace {
16043 // Documents can stay alive for days. We don't want to update the permission
16044 // value at any user-interaction, and, using a timer triggered any X seconds
16045 // should be good enough. 'X' is taken from
16046 // privacy.userInteraction.document.interval pref.
16047 // We also want to store the user-interaction before shutting down, and, for
16048 // this reason, this class implements nsIAsyncShutdownBlocker interface.
16049 class UserInteractionTimer final : public Runnable,
16050 public nsITimerCallback,
16051 public nsIAsyncShutdownBlocker {
16052 public:
16053 NS_DECL_ISUPPORTS_INHERITED
16055 explicit UserInteractionTimer(Document* aDocument)
16056 : Runnable("UserInteractionTimer"),
16057 mPrincipal(aDocument->NodePrincipal()),
16058 mDocument(do_GetWeakReference(aDocument)) {
16059 static int32_t userInteractionTimerId = 0;
16060 // Blocker names must be unique. Let's create it now because when needed,
16061 // the document could be already gone.
16062 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
16063 ++userInteractionTimerId, aDocument);
16066 // Runnable interface
16068 NS_IMETHOD
16069 Run() override {
16070 uint32_t interval =
16071 StaticPrefs::privacy_userInteraction_document_interval();
16072 if (!interval) {
16073 return NS_OK;
16076 RefPtr<UserInteractionTimer> self = this;
16077 auto raii =
16078 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
16080 nsresult rv = NS_NewTimerWithCallback(
16081 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
16082 NS_ENSURE_SUCCESS(rv, NS_OK);
16084 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16085 NS_ENSURE_TRUE(!!phase, NS_OK);
16087 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
16088 __LINE__, u"UserInteractionTimer shutdown"_ns);
16089 NS_ENSURE_SUCCESS(rv, NS_OK);
16091 raii.release();
16092 return NS_OK;
16095 // nsITimerCallback interface
16097 NS_IMETHOD
16098 Notify(nsITimer* aTimer) override {
16099 StoreUserInteraction();
16100 return NS_OK;
16103 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
16104 using nsINamed::GetName;
16105 #endif
16107 // nsIAsyncShutdownBlocker interface
16109 NS_IMETHOD
16110 GetName(nsAString& aName) override {
16111 aName = mBlockerName;
16112 return NS_OK;
16115 NS_IMETHOD
16116 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
16117 CancelTimerAndStoreUserInteraction();
16118 return NS_OK;
16121 NS_IMETHOD
16122 GetState(nsIPropertyBag**) override { return NS_OK; }
16124 private:
16125 ~UserInteractionTimer() = default;
16127 void StoreUserInteraction() {
16128 // Remove the shutting down blocker
16129 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16130 if (phase) {
16131 phase->RemoveBlocker(this);
16134 // If the document is not gone, let's reset its timer flag.
16135 nsCOMPtr<Document> document = do_QueryReferent(mDocument);
16136 if (document) {
16137 ContentBlockingUserInteraction::Observe(mPrincipal);
16138 document->ResetUserInteractionTimer();
16142 void CancelTimerAndStoreUserInteraction() {
16143 if (mTimer) {
16144 mTimer->Cancel();
16145 mTimer = nullptr;
16148 StoreUserInteraction();
16151 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
16152 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
16153 NS_ENSURE_TRUE(!!svc, nullptr);
16155 nsCOMPtr<nsIAsyncShutdownClient> phase;
16156 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
16157 NS_ENSURE_SUCCESS(rv, nullptr);
16159 return phase.forget();
16162 nsCOMPtr<nsIPrincipal> mPrincipal;
16163 nsWeakPtr mDocument;
16165 nsCOMPtr<nsITimer> mTimer;
16167 nsString mBlockerName;
16170 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
16171 nsIAsyncShutdownBlocker)
16173 } // namespace
16175 void Document::MaybeStoreUserInteractionAsPermission() {
16176 // We care about user-interaction stored only for top-level documents.
16177 if (!IsTopLevelContentDocument()) {
16178 return;
16181 if (!mUserHasInteracted) {
16182 // First interaction, let's store this info now.
16183 ContentBlockingUserInteraction::Observe(NodePrincipal());
16184 return;
16187 if (mHasUserInteractionTimerScheduled) {
16188 return;
16191 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
16192 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
16193 EventQueuePriority::Idle);
16194 if (NS_WARN_IF(NS_FAILED(rv))) {
16195 return;
16198 // This value will be reset by the timer.
16199 mHasUserInteractionTimerScheduled = true;
16202 void Document::ResetUserInteractionTimer() {
16203 mHasUserInteractionTimerScheduled = false;
16206 bool Document::IsExtensionPage() const {
16207 return Preferences::GetBool("media.autoplay.allow-extension-background-pages",
16208 true) &&
16209 BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
16212 void Document::TraceProtos(JSTracer* aTrc) {
16213 if (mPrototypeDocument) {
16214 mPrototypeDocument->TraceProtos(aTrc);
16219 * Retrieves the classification of the Flash plugins in the document based on
16220 * the classification lists. For more information, see
16221 * toolkit/components/url-classifier/flash-block-lists.rst
16223 FlashClassification Document::DocumentFlashClassification() {
16224 // Disable flash blocking when fission is enabled(See Bug 1584931).
16225 const auto fnIsFlashBlockingEnabled = [] {
16226 return StaticPrefs::plugins_flashBlock_enabled() && !FissionAutostart();
16229 // If neither pref is on, skip the null-principal and principal URI checks.
16230 if (!StaticPrefs::plugins_http_https_only() && !fnIsFlashBlockingEnabled()) {
16231 return FlashClassification::Unknown;
16234 if (!NodePrincipal()->GetIsContentPrincipal()) {
16235 return FlashClassification::Denied;
16238 if (StaticPrefs::plugins_http_https_only()) {
16239 // Only allow plugins for documents from an HTTP/HTTPS origin. This should
16240 // allow dependent data: URIs to load plugins, but not:
16241 // * chrome documents
16242 // * "bare" data: loads
16243 // * FTP/gopher/file
16245 if (!(NodePrincipal()->SchemeIs("http") ||
16246 NodePrincipal()->SchemeIs("https"))) {
16247 return FlashClassification::Denied;
16251 // If flash blocking is disabled, it is equivalent to all sites being
16252 // on neither list.
16253 if (!fnIsFlashBlockingEnabled()) {
16254 return FlashClassification::Unknown;
16257 if (mFlashClassification == FlashClassification::Unknown) {
16258 mFlashClassification = DocumentFlashClassificationInternal();
16261 return mFlashClassification;
16264 void Document::AddResizeObserver(ResizeObserver& aObserver) {
16265 if (!mResizeObserverController) {
16266 mResizeObserverController = MakeUnique<ResizeObserverController>(this);
16268 mResizeObserverController->AddResizeObserver(aObserver);
16271 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
16272 MOZ_DIAGNOSTIC_ASSERT(mResizeObserverController, "No controller?");
16273 if (MOZ_UNLIKELY(!mResizeObserverController)) {
16274 return;
16276 mResizeObserverController->RemoveResizeObserver(aObserver);
16279 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
16280 if (!mPermissionDelegateHandler) {
16281 mPermissionDelegateHandler =
16282 mozilla::MakeAndAddRef<PermissionDelegateHandler>(this);
16285 if (!mPermissionDelegateHandler->Initialize()) {
16286 mPermissionDelegateHandler = nullptr;
16289 return mPermissionDelegateHandler;
16292 void Document::ScheduleResizeObserversNotification() const {
16293 if (!mResizeObserverController) {
16294 return;
16297 mResizeObserverController->ScheduleNotification();
16301 * Initializes |mIsThirdPartyForFlashClassifier| if necessary and returns its
16302 * value. The value returned represents whether this document should be
16303 * considered Third-Party.
16305 * A top-level document cannot be a considered Third-Party; only subdocuments
16306 * may. For a subdocument to be considered Third-Party, it must meet ANY ONE
16307 * of the following requirements:
16308 * - The document's parent is Third-Party
16309 * - The document has a different scheme (http/https) than its parent document
16310 * - The document's domain and subdomain do not match those of its parent
16311 * document.
16313 * If there is an error in determining whether the document is Third-Party,
16314 * it will be assumed to be Third-Party for security reasons.
16316 bool Document::IsThirdPartyForFlashClassifier() {
16317 if (mIsThirdPartyForFlashClassifier.isSome()) {
16318 return mIsThirdPartyForFlashClassifier.value();
16321 BrowsingContext* browsingContext = this->GetBrowsingContext();
16322 if (!browsingContext) {
16323 mIsThirdPartyForFlashClassifier.emplace(true);
16324 return mIsThirdPartyForFlashClassifier.value();
16327 if (browsingContext->IsTop()) {
16328 mIsThirdPartyForFlashClassifier.emplace(false);
16329 return mIsThirdPartyForFlashClassifier.value();
16332 nsCOMPtr<Document> parentDocument = GetInProcessParentDocument();
16333 if (!parentDocument) {
16334 // Failure
16335 mIsThirdPartyForFlashClassifier.emplace(true);
16336 return mIsThirdPartyForFlashClassifier.value();
16339 if (parentDocument->IsThirdPartyForFlashClassifier()) {
16340 mIsThirdPartyForFlashClassifier.emplace(true);
16341 return mIsThirdPartyForFlashClassifier.value();
16344 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
16345 nsCOMPtr<nsIPrincipal> parentPrincipal = parentDocument->GetPrincipal();
16347 bool principalsMatch = false;
16348 nsresult rv = principal->Equals(parentPrincipal, &principalsMatch);
16350 if (NS_WARN_IF(NS_FAILED(rv))) {
16351 // Failure
16352 mIsThirdPartyForFlashClassifier.emplace(true);
16353 return mIsThirdPartyForFlashClassifier.value();
16356 if (!principalsMatch) {
16357 mIsThirdPartyForFlashClassifier.emplace(true);
16358 return mIsThirdPartyForFlashClassifier.value();
16361 // Fall-through. Document is not a Third-Party Document.
16362 mIsThirdPartyForFlashClassifier.emplace(false);
16363 return mIsThirdPartyForFlashClassifier.value();
16366 FlashClassification Document::DocumentFlashClassificationInternal() {
16367 FlashClassification classification = FlashClassification::Unknown;
16369 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(GetChannel());
16370 if (httpChannel) {
16371 nsIHttpChannel::FlashPluginState state = nsIHttpChannel::FlashPluginUnknown;
16372 httpChannel->GetFlashPluginState(&state);
16374 // Allow unknown children to inherit allowed status from parent, but do not
16375 // allow denied children to do so.
16377 if (state == nsIHttpChannel::FlashPluginDeniedInSubdocuments &&
16378 IsThirdPartyForFlashClassifier()) {
16379 return FlashClassification::Denied;
16382 if (state == nsIHttpChannel::FlashPluginDenied) {
16383 return FlashClassification::Denied;
16386 if (state == nsIHttpChannel::FlashPluginAllowed) {
16387 classification = FlashClassification::Allowed;
16391 if (IsTopLevelContentDocument()) {
16392 return classification;
16395 Document* parentDocument = GetInProcessParentDocument();
16396 if (!parentDocument) {
16397 return FlashClassification::Denied;
16400 FlashClassification parentClassification =
16401 parentDocument->DocumentFlashClassification();
16403 if (parentClassification == FlashClassification::Denied) {
16404 return FlashClassification::Denied;
16407 // Allow unknown children to inherit allowed status from parent, but
16408 // do not allow denied children to do so.
16409 if (classification == FlashClassification::Unknown &&
16410 parentClassification == FlashClassification::Allowed) {
16411 return FlashClassification::Allowed;
16414 return classification;
16417 void Document::ClearStaleServoData() {
16418 DocumentStyleRootIterator iter(this);
16419 while (Element* root = iter.GetNextStyleRoot()) {
16420 RestyleManager::ClearServoDataFromSubtree(root);
16424 Selection* Document::GetSelection(ErrorResult& aRv) {
16425 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
16426 if (!window) {
16427 return nullptr;
16430 if (!window->IsCurrentInnerWindow()) {
16431 return nullptr;
16434 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
16437 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
16438 mozilla::ErrorResult& aRv) {
16439 nsIGlobalObject* global = GetScopeObject();
16440 if (!global) {
16441 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
16442 return nullptr;
16445 RefPtr<Promise> promise =
16446 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
16447 if (aRv.Failed()) {
16448 return nullptr;
16451 if (NodePrincipal()->GetIsNullPrincipal()) {
16452 promise->MaybeResolve(false);
16453 return promise.forget();
16456 if (IsTopLevelContentDocument()) {
16457 promise->MaybeResolve(true);
16458 return promise.forget();
16461 RefPtr<BrowsingContext> bc = GetBrowsingContext();
16462 if (!bc) {
16463 promise->MaybeResolve(false);
16464 return promise.forget();
16467 RefPtr<BrowsingContext> topBC = bc->Top();
16468 // We check if the document is a first-party document here by testing if the
16469 // top-level window is same-origin. In non-Fission mode, we can directly get
16470 // the top-level window through the top browsing context since it should be
16471 // in-process. And test their principals.
16473 // In Fission mode, we can also directly get the top-level window. If we
16474 // cannot get it, this means the top-level window is cross-origin. Then, we
16475 // know our answer.
16476 if (auto* topOuterWindow = topBC->GetDOMWindow()) {
16477 if (nsGlobalWindowOuter::Cast(topOuterWindow)
16478 ->GetPrincipal()
16479 ->Equals(NodePrincipal())) {
16480 promise->MaybeResolve(true);
16481 return promise.forget();
16485 nsPIDOMWindowInner* inner = GetInnerWindow();
16486 nsGlobalWindowOuter* outer = nullptr;
16487 if (inner) {
16488 outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16489 promise->MaybeResolve(outer->IsStorageAccessPermissionGranted());
16490 } else {
16491 promise->MaybeRejectWithUndefined();
16493 return promise.forget();
16496 RefPtr<Document::GetContentBlockingEventsPromise>
16497 Document::GetContentBlockingEvents() {
16498 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
16499 if (!inner) {
16500 return nullptr;
16503 RefPtr<WindowGlobalChild> wgc = inner->GetWindowGlobalChild();
16504 if (!wgc) {
16505 return nullptr;
16508 return wgc->SendGetContentBlockingEvents()->Then(
16509 GetCurrentSerialEventTarget(), __func__,
16510 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
16511 ResolveOrRejectValue& aValue) {
16512 if (aValue.IsResolve()) {
16513 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
16514 aValue.ResolveValue(), __func__);
16517 return Document::GetContentBlockingEventsPromise::CreateAndReject(
16518 false, __func__);
16522 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
16523 mozilla::ErrorResult& aRv) {
16524 nsIGlobalObject* global = GetScopeObject();
16525 if (!global) {
16526 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
16527 return nullptr;
16530 // Propagate user input event handling to the resolve handler
16531 RefPtr<Promise> promise =
16532 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
16533 if (aRv.Failed()) {
16534 return nullptr;
16537 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
16538 if (!inner) {
16539 promise->MaybeRejectWithUndefined();
16540 return promise.forget();
16543 // Step 1. If the document already has been granted access, resolve.
16544 RefPtr<nsGlobalWindowOuter> outer =
16545 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16546 if (!outer) {
16547 promise->MaybeRejectWithUndefined();
16548 return promise.forget();
16551 if (outer->IsStorageAccessPermissionGranted()) {
16552 promise->MaybeResolveWithUndefined();
16553 return promise.forget();
16556 // Step 2. If the document has a null origin, reject.
16557 if (NodePrincipal()->GetIsNullPrincipal()) {
16558 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
16559 nsLiteralCString("requestStorageAccess"),
16560 this, nsContentUtils::eDOM_PROPERTIES,
16561 "RequestStorageAccessNullPrincipal");
16562 promise->MaybeRejectWithUndefined();
16563 return promise.forget();
16566 RefPtr<BrowsingContext> bc = GetBrowsingContext();
16567 if (!bc) {
16568 promise->MaybeRejectWithUndefined();
16569 return promise.forget();
16572 // Only enforce third-party checks when there is a reason to enforce them.
16573 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16574 // Step 3. If the document's frame is the main frame, resolve.
16575 if (IsTopLevelContentDocument()) {
16576 promise->MaybeResolveWithUndefined();
16577 return promise.forget();
16580 // Step 4. If the sub frame's origin is equal to the main frame's, resolve.
16582 // In fission, if the sub frame's origin differs from the main frame's
16583 // origin, they will be in different processes. We use IsInProcess()
16584 // check here to deterimine whether they have the same origin. In
16585 // non-fission mode, it is always in-process so we need to compare their
16586 // principals.
16587 if (bc->Top()->IsInProcess()) {
16588 nsCOMPtr<nsPIDOMWindowOuter> topOuter = bc->Top()->GetDOMWindow();
16589 if (!topOuter) {
16590 promise->MaybeRejectWithUndefined();
16591 return promise.forget();
16594 nsCOMPtr<Document> topLevelDoc = topOuter->GetExtantDoc();
16595 if (!topLevelDoc) {
16596 promise->MaybeRejectWithUndefined();
16597 return promise.forget();
16600 if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
16601 promise->MaybeResolveWithUndefined();
16602 return promise.forget();
16607 // Step 5. If the sub frame is not sandboxed, skip to step 7.
16608 // Step 6. If the sub frame doesn't have the token
16609 // "allow-storage-access-by-user-activation", reject.
16610 if (StorageAccessSandboxed()) {
16611 nsContentUtils::ReportToConsole(
16612 nsIScriptError::errorFlag, nsLiteralCString("requestStorageAccess"),
16613 this, nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessSandboxed");
16614 promise->MaybeRejectWithUndefined();
16615 return promise.forget();
16618 // Step 7. If the sub frame's parent frame is not the top frame, reject.
16619 RefPtr<BrowsingContext> parentBC = bc->GetParent();
16620 if (parentBC && !parentBC->IsTopContent()) {
16621 nsContentUtils::ReportToConsole(
16622 nsIScriptError::errorFlag, nsLiteralCString("requestStorageAccess"),
16623 this, nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessNested");
16624 promise->MaybeRejectWithUndefined();
16625 return promise.forget();
16628 // Step 8. If the browser is not processing a user gesture, reject.
16629 if (!UserActivation::IsHandlingUserInput()) {
16630 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
16631 nsLiteralCString("requestStorageAccess"),
16632 this, nsContentUtils::eDOM_PROPERTIES,
16633 "RequestStorageAccessUserGesture");
16634 promise->MaybeRejectWithUndefined();
16635 return promise.forget();
16638 // Step 9. Check any additional rules that the browser has.
16639 // Examples: skip-lists, on-device classification,
16640 // user settings, anti-clickjacking heuristics, or prompting the
16641 // user for explicit permission. Reject if some rule is not fulfilled.
16642 if (CookieJarSettings()->GetRejectThirdPartyContexts()) {
16643 // Only do something special for third-party tracking content.
16644 uint32_t antiTrackingRejectedReason = 0;
16645 if (StorageDisabledByAntiTracking(this, nullptr,
16646 antiTrackingRejectedReason)) {
16647 // If storage is disabled because of a custom cookie permission for the
16648 // site, reject.
16649 if (antiTrackingRejectedReason ==
16650 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) {
16651 promise->MaybeRejectWithUndefined();
16652 return promise.forget();
16655 // Note: If this has returned true, the top-level document is guaranteed
16656 // to not be on the Content Blocking allow list.
16657 MOZ_ASSERT(!CookieJarSettings()->GetIsOnContentBlockingAllowList());
16659 RefPtr<Document> self(this);
16661 auto performFinalChecks =
16662 [inner,
16663 self]() -> RefPtr<ContentBlocking::StorageAccessFinalCheckPromise> {
16664 RefPtr<ContentBlocking::StorageAccessFinalCheckPromise::Private> p =
16665 new ContentBlocking::StorageAccessFinalCheckPromise::Private(
16666 __func__);
16667 RefPtr<StorageAccessPermissionRequest> sapr =
16668 StorageAccessPermissionRequest::Create(
16669 inner,
16670 // Allow
16671 [p] {
16672 Telemetry::AccumulateCategorical(
16673 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
16674 p->Resolve(ContentBlocking::eAllow, __func__);
16676 // Block
16677 [p] {
16678 Telemetry::AccumulateCategorical(
16679 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
16680 p->Reject(false, __func__);
16683 using PromptResult = ContentPermissionRequestBase::PromptResult;
16684 PromptResult pr = sapr->CheckPromptPrefs();
16686 if (pr == PromptResult::Pending) {
16687 // We're about to show a prompt, record the request attempt
16688 Telemetry::AccumulateCategorical(
16689 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
16692 self->AutomaticStorageAccessPermissionCanBeGranted()->Then(
16693 GetCurrentSerialEventTarget(), __func__,
16694 [p, pr, sapr,
16695 inner](const AutomaticStorageAccessPermissionGrantPromise::
16696 ResolveOrRejectValue& aValue) -> void {
16697 // Make a copy because we can't modified copy-captured lambda
16698 // variables.
16699 PromptResult pr2 = pr;
16701 bool storageAccessCanBeGrantedAutomatically =
16702 aValue.IsResolve() && aValue.ResolveValue();
16704 bool autoGrant = false;
16705 if (pr2 == PromptResult::Pending &&
16706 storageAccessCanBeGrantedAutomatically) {
16707 pr2 = PromptResult::Granted;
16708 autoGrant = true;
16710 Telemetry::AccumulateCategorical(
16711 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
16712 AllowAutomatically);
16715 if (pr2 != PromptResult::Pending) {
16716 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
16717 pr2 == PromptResult::Denied);
16718 if (pr2 == PromptResult::Granted) {
16719 ContentBlocking::StorageAccessPromptChoices choice =
16720 ContentBlocking::eAllow;
16721 if (autoGrant) {
16722 choice = ContentBlocking::eAllowAutoGrant;
16724 if (!autoGrant) {
16725 p->Resolve(choice, __func__);
16726 } else {
16727 sapr->MaybeDelayAutomaticGrants()->Then(
16728 GetCurrentSerialEventTarget(), __func__,
16729 [p, choice] { p->Resolve(choice, __func__); },
16730 [p] { p->Reject(false, __func__); });
16732 return;
16734 p->Reject(false, __func__);
16735 return;
16738 sapr->RequestDelayedTask(
16739 inner->EventTargetFor(TaskCategory::Other),
16740 ContentPermissionRequestBase::DelayedTaskType::Request);
16743 return std::move(p);
16745 ContentBlocking::AllowAccessFor(
16746 NodePrincipal(), bc, ContentBlockingNotifier::eStorageAccessAPI,
16747 performFinalChecks)
16748 ->Then(
16749 GetCurrentSerialEventTarget(), __func__,
16750 [outer, promise] {
16751 // Step 10. Grant the document access to cookies and store
16752 // that fact for
16753 // the purposes of future calls to
16754 // hasStorageAccess() and requestStorageAccess().
16755 outer->SetStorageAccessPermissionGranted(true);
16756 promise->MaybeResolveWithUndefined();
16758 [outer, promise] {
16759 outer->SetStorageAccessPermissionGranted(false);
16760 promise->MaybeRejectWithUndefined();
16763 return promise.forget();
16767 outer->SetStorageAccessPermissionGranted(true);
16768 promise->MaybeResolveWithUndefined();
16769 return promise.forget();
16772 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
16773 Document::AutomaticStorageAccessPermissionCanBeGranted() {
16774 if (XRE_IsContentProcess()) {
16775 // In the content process, we need to ask the parent process to compute
16776 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
16777 // isn't accessible in the content process.
16778 ContentChild* cc = ContentChild::GetSingleton();
16779 MOZ_ASSERT(cc);
16781 return cc
16782 ->SendAutomaticStorageAccessPermissionCanBeGranted(
16783 IPC::Principal(NodePrincipal()))
16784 ->Then(GetCurrentSerialEventTarget(), __func__,
16785 [](const ContentChild::
16786 AutomaticStorageAccessPermissionCanBeGrantedPromise::
16787 ResolveOrRejectValue& aValue) {
16788 if (aValue.IsResolve()) {
16789 return AutomaticStorageAccessPermissionGrantPromise::
16790 CreateAndResolve(aValue.ResolveValue(), __func__);
16793 return AutomaticStorageAccessPermissionGrantPromise::
16794 CreateAndReject(false, __func__);
16798 if (XRE_IsParentProcess()) {
16799 // In the parent process, we can directly compute this.
16800 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
16801 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
16802 __func__);
16805 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
16806 false, __func__);
16809 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
16810 nsIPrincipal* aPrincipal) {
16811 nsAutoCString prefix;
16812 AntiTrackingUtils::CreateStoragePermissionKey(aPrincipal, prefix);
16814 PermissionManager* permManager = PermissionManager::GetInstance();
16815 if (NS_WARN_IF(!permManager)) {
16816 return false;
16819 using Permissions = nsTArray<RefPtr<nsIPermission>>;
16820 Permissions perms;
16821 nsresult rv = permManager->GetAllWithTypePrefix(prefix, perms);
16822 if (NS_WARN_IF(NS_FAILED(rv))) {
16823 return false;
16826 nsAutoCString prefix2(prefix);
16827 prefix2.Append('^');
16828 using Origins = nsTArray<nsCString>;
16829 Origins origins;
16831 for (const auto& perm : perms) {
16832 nsAutoCString type;
16833 rv = perm->GetType(type);
16834 if (NS_WARN_IF(NS_FAILED(rv))) {
16835 return false;
16837 // Let's make sure that we're not looking at a permission for
16838 // https://exampletracker.company when we mean to look for the
16839 // permission for https://exampletracker.com!
16840 if (type != prefix && StringHead(type, prefix2.Length()) != prefix2) {
16841 continue;
16844 nsCOMPtr<nsIPrincipal> principal;
16845 rv = perm->GetPrincipal(getter_AddRefs(principal));
16846 if (NS_WARN_IF(NS_FAILED(rv))) {
16847 return false;
16850 nsAutoCString origin;
16851 rv = principal->GetOrigin(origin);
16852 if (NS_WARN_IF(NS_FAILED(rv))) {
16853 return false;
16856 ToLowerCase(origin);
16858 if (origins.IndexOf(origin) == Origins::NoIndex) {
16859 origins.AppendElement(origin);
16863 nsCOMPtr<nsIBrowserUsage> bu = do_ImportModule(
16864 "resource:///modules/BrowserUsageTelemetry.jsm", fallible);
16865 if (NS_WARN_IF(!bu)) {
16866 return false;
16869 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
16870 rv = bu->GetUniqueDomainsVisitedInPast24Hours(
16871 &uniqueDomainsVisitedInPast24Hours);
16872 if (NS_WARN_IF(NS_FAILED(rv))) {
16873 return false;
16876 // one percent of the number of top-levels origins visited in the current
16877 // session (but not to exceed 24 hours), or the value of the
16878 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
16879 // higher.
16880 size_t maxConcurrentAutomaticGrants = std::max(
16881 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
16882 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
16885 size_t originsThirdPartyHasAccessTo = origins.Length();
16887 return StaticPrefs::dom_storage_access_auto_grants() &&
16888 originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
16891 void Document::RecordNavigationTiming(ReadyState aReadyState) {
16892 if (!XRE_IsContentProcess()) {
16893 return;
16895 if (!IsTopLevelContentDocument()) {
16896 return;
16898 // If we dont have the timing yet (mostly because the doc is still loading),
16899 // get it from docshell.
16900 RefPtr<nsDOMNavigationTiming> timing = mTiming;
16901 if (!timing) {
16902 if (!mDocumentContainer) {
16903 return;
16905 timing = mDocumentContainer->GetNavigationTiming();
16906 if (!timing) {
16907 return;
16910 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
16911 switch (aReadyState) {
16912 case READYSTATE_LOADING:
16913 if (!mDOMLoadingSet) {
16914 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
16915 startTime);
16916 mDOMLoadingSet = true;
16918 break;
16919 case READYSTATE_INTERACTIVE:
16920 if (!mDOMInteractiveSet) {
16921 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
16922 startTime);
16923 mDOMInteractiveSet = true;
16925 break;
16926 case READYSTATE_COMPLETE:
16927 if (!mDOMCompleteSet) {
16928 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
16929 startTime);
16930 mDOMCompleteSet = true;
16932 break;
16933 default:
16934 NS_WARNING("Unexpected ReadyState value");
16935 break;
16939 bool Document::ModuleScriptsEnabled() {
16940 return nsContentUtils::IsChromeDoc(this) ||
16941 StaticPrefs::dom_moduleScripts_enabled();
16944 void Document::ReportShadowDOMUsage() {
16945 nsPIDOMWindowInner* inner = GetInnerWindow();
16946 if (NS_WARN_IF(!inner)) {
16947 return;
16950 WindowContext* wc = inner->GetWindowContext();
16951 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
16952 return;
16955 WindowContext* topWc = wc->TopWindowContext();
16956 if (topWc->GetHasReportedShadowDOMUsage()) {
16957 return;
16960 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
16963 // static
16964 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
16965 return StaticPrefs::dom_storage_access_enabled() &&
16966 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
16969 bool Document::StorageAccessSandboxed() const {
16970 return Document::StorageAccessSandboxed(GetSandboxFlags());
16973 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
16974 if (mCachedTabSizeGeneration == 0 ||
16975 GetGeneration() != mCachedTabSizeGeneration) {
16976 return false;
16978 aSizes->mDom += mCachedTabSizes.mDom;
16979 aSizes->mStyle += mCachedTabSizes.mStyle;
16980 aSizes->mOther += mCachedTabSizes.mOther;
16981 return true;
16984 void Document::SetCachedSizes(nsTabSizes* aSizes) {
16985 mCachedTabSizes.mDom = aSizes->mDom;
16986 mCachedTabSizes.mStyle = aSizes->mStyle;
16987 mCachedTabSizes.mOther = aSizes->mOther;
16988 mCachedTabSizeGeneration = GetGeneration();
16991 already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
16992 nsAutoString contentLang;
16993 GetContentLanguage(contentLang);
16994 contentLang.StripWhitespace();
16996 // Content-Language may be a comma-separated list of language codes,
16997 // in which case the HTML5 spec says to treat it as unknown
16998 if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
16999 return NS_Atomize(contentLang);
17002 return nullptr;
17005 already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
17006 RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
17007 if (!lang) {
17008 lang = mLanguageFromCharset;
17010 return lang.forget();
17013 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
17014 nsAtom* aLanguage, bool* aNeedsToCache) const {
17015 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
17016 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
17019 void Document::DoCacheAllKnownLangPrefs() {
17020 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
17021 RefPtr<nsAtom> lang = GetLanguageForStyle();
17022 StaticPresData* data = StaticPresData::Get();
17023 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
17024 data->GetFontPrefsForLang(nsGkAtoms::x_math);
17025 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
17026 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
17027 for (const auto& key : mLanguagesUsed) {
17028 data->GetFontPrefsForLang(key);
17030 mMayNeedFontPrefsUpdate = false;
17033 void Document::RecomputeLanguageFromCharset() {
17034 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
17035 RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
17036 if (language == nsGkAtoms::Unicode) {
17037 language = service->GetLocaleLanguage();
17040 if (language == mLanguageFromCharset) {
17041 return;
17044 mMayNeedFontPrefsUpdate = true;
17045 mLanguageFromCharset = std::move(language);
17048 nsICookieJarSettings* Document::CookieJarSettings() {
17049 // If we are here, this is probably a javascript: URL document. In any case,
17050 // we must have a nsCookieJarSettings. Let's create it.
17051 if (!mCookieJarSettings) {
17052 Document* inProcessParent = GetInProcessParentDocument();
17054 mCookieJarSettings =
17055 inProcessParent
17056 ? net::CookieJarSettings::Create(
17057 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
17058 mozilla::net::CookieJarSettings::Cast(
17059 inProcessParent->CookieJarSettings())
17060 ->GetPartitionKey(),
17061 inProcessParent->CookieJarSettings()
17062 ->GetIsFirstPartyIsolated(),
17063 inProcessParent->CookieJarSettings()
17064 ->GetIsOnContentBlockingAllowList())
17065 : net::CookieJarSettings::Create(NodePrincipal());
17067 if (auto* wgc = GetWindowGlobalChild()) {
17068 net::CookieJarSettingsArgs csArgs;
17069 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
17070 // Update cookie settings in the parent process
17071 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
17072 NS_WARNING(
17073 "Failed to update document's cookie jar settings on the "
17074 "WindowGlobalParent");
17079 return mCookieJarSettings;
17082 bool Document::HasStorageAccessPermissionGranted() {
17083 // The HasStoragePermission flag in LoadInfo remains fixed when
17084 // it is set in the parent process, so we need to check the cache
17085 // to see if the permission is granted afterwards.
17086 nsPIDOMWindowInner* inner = GetInnerWindow();
17087 if (inner && inner->HasStorageAccessPermissionGranted()) {
17088 return true;
17091 if (!mChannel) {
17092 return false;
17095 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17096 return loadInfo->GetHasStoragePermission();
17099 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
17100 nsPIDOMWindowInner* inner = GetInnerWindow();
17101 if (!inner) {
17102 return NodePrincipal();
17105 // Return our cached storage principal if one exists.
17106 if (mActiveStoragePrincipal) {
17107 return mActiveStoragePrincipal;
17110 // We use the lower-level ContentBlocking API here to ensure this
17111 // check doesn't send notifications.
17112 uint32_t rejectedReason = 0;
17113 if (ContentBlocking::ShouldAllowAccessFor(inner, GetDocumentURI(),
17114 &rejectedReason)) {
17115 return mActiveStoragePrincipal = NodePrincipal();
17118 // Let's use the storage principal only if we need to partition the cookie
17119 // jar. When the permission is granted, access will be different and the
17120 // normal principal will be used.
17121 if (ShouldPartitionStorage(rejectedReason) &&
17122 !StoragePartitioningEnabled(
17123 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
17124 return mActiveStoragePrincipal = NodePrincipal();
17127 return mActiveStoragePrincipal = mPartitionedPrincipal;
17130 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
17131 // If the document is sandboxed document or data: document, we should
17132 // get URI of the parent document.
17133 for (const Document* document = this;
17134 document && document->IsContentDocument();
17135 document = document->GetInProcessParentDocument()) {
17136 // The document URI may be about:blank even if it comes from actual web
17137 // site. Therefore, we need to check the URI of its principal.
17138 nsIPrincipal* principal = document->NodePrincipal();
17139 if (principal->GetIsNullPrincipal()) {
17140 continue;
17142 return principal;
17144 return nullptr;
17147 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
17148 mIsInitialDocumentInWindow = aIsInitialDocument;
17150 // Asynchronously tell the parent process that we are, or are no longer, the
17151 // initial document. This happens async.
17152 if (auto* wgc = GetWindowGlobalChild()) {
17153 wgc->SendSetIsInitialDocument(aIsInitialDocument);
17157 // static
17158 void Document::AddToplevelLoadingDocument(Document* aDoc) {
17159 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
17160 // Currently we're interested in foreground documents only, so bail out early.
17161 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
17162 return;
17165 if (!sLoadingForegroundTopLevelContentDocument) {
17166 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
17167 mozilla::ipc::IdleSchedulerChild* idleScheduler =
17168 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
17169 if (idleScheduler) {
17170 idleScheduler->SendRunningPrioritizedOperation();
17173 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
17174 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
17178 // static
17179 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
17180 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
17181 if (sLoadingForegroundTopLevelContentDocument) {
17182 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
17183 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
17184 delete sLoadingForegroundTopLevelContentDocument;
17185 sLoadingForegroundTopLevelContentDocument = nullptr;
17187 mozilla::ipc::IdleSchedulerChild* idleScheduler =
17188 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
17189 if (idleScheduler) {
17190 idleScheduler->SendPrioritizedOperationDone();
17196 StylePrefersColorScheme Document::PrefersColorScheme(
17197 IgnoreRFP aIgnoreRFP) const {
17198 if (aIgnoreRFP == IgnoreRFP::No &&
17199 nsContentUtils::ShouldResistFingerprinting(this)) {
17200 return StylePrefersColorScheme::Light;
17203 if (auto* bc = GetBrowsingContext()) {
17204 switch (bc->Top()->PrefersColorSchemeOverride()) {
17205 case dom::PrefersColorSchemeOverride::Dark:
17206 return StylePrefersColorScheme::Dark;
17207 case dom::PrefersColorSchemeOverride::Light:
17208 return StylePrefersColorScheme::Light;
17209 case dom::PrefersColorSchemeOverride::None:
17210 case dom::PrefersColorSchemeOverride::EndGuard_:
17211 break;
17215 if (nsPresContext* pc = GetPresContext()) {
17216 if (pc->IsPrintingOrPrintPreview()) {
17217 return StylePrefersColorScheme::Light;
17221 if (!nsContentUtils::IsChromeDoc(this)) {
17222 switch (StaticPrefs::layout_css_prefers_color_scheme_content_override()) {
17223 case 0:
17224 return StylePrefersColorScheme::Dark;
17225 case 1:
17226 return StylePrefersColorScheme::Light;
17227 default:
17228 break;
17232 const bool dark =
17233 !!LookAndFeel::GetInt(LookAndFeel::IntID::SystemUsesDarkTheme, 0);
17234 return dark ? StylePrefersColorScheme::Dark : StylePrefersColorScheme::Light;
17237 // static
17238 bool Document::UseOverlayScrollbars(const Document* aDocument) {
17239 BrowsingContext* bc = aDocument ? aDocument->GetBrowsingContext() : nullptr;
17240 return LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) ||
17241 (bc && bc->InRDMPane());
17244 bool Document::HasRecentlyStartedForegroundLoads() {
17245 if (!sLoadingForegroundTopLevelContentDocument) {
17246 return false;
17249 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
17250 ++i) {
17251 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
17252 // A page loaded in foreground could be in background now.
17253 if (!doc->IsInBackgroundWindow()) {
17254 nsPIDOMWindowInner* win = doc->GetInnerWindow();
17255 if (win) {
17256 Performance* perf = win->GetPerformance();
17257 if (perf &&
17258 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
17259 return true;
17265 // Didn't find any loading foreground documents, just clear the array.
17266 delete sLoadingForegroundTopLevelContentDocument;
17267 sLoadingForegroundTopLevelContentDocument = nullptr;
17269 mozilla::ipc::IdleSchedulerChild* idleScheduler =
17270 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
17271 if (idleScheduler) {
17272 idleScheduler->SendPrioritizedOperationDone();
17274 return false;
17277 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
17278 nsFrameLoader* aStaticCloneOf) {
17279 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
17280 clone->mElement = aElement;
17281 clone->mStaticCloneOf = aStaticCloneOf;
17284 bool Document::ShouldAvoidNativeTheme() const {
17285 return StaticPrefs::widget_non_native_theme_enabled() &&
17286 (!IsInChromeDocShell() || XRE_IsContentProcess());
17289 bool Document::UseRegularPrincipal() const {
17290 return EffectiveStoragePrincipal() == NodePrincipal();
17293 bool Document::HasThirdPartyChannel() {
17294 nsCOMPtr<nsIChannel> channel = GetChannel();
17295 if (channel) {
17296 // We assume that the channel is a third-party by default.
17297 bool thirdParty = true;
17299 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
17300 components::ThirdPartyUtil::Service();
17301 if (!thirdPartyUtil) {
17302 return thirdParty;
17305 // Check that if the channel is a third-party to its parent.
17306 nsresult rv =
17307 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
17308 if (NS_FAILED(rv)) {
17309 // Assume third-party in case of failure
17310 thirdParty = true;
17313 return thirdParty;
17316 if (mParentDocument) {
17317 return mParentDocument->HasThirdPartyChannel();
17320 return false;
17323 bool Document::ShouldIncludeInTelemetry(bool aAllowExtensionURIs) {
17324 if (!(IsContentDocument() || IsResourceDoc())) {
17325 return false;
17328 if (!aAllowExtensionURIs &&
17329 NodePrincipal()->GetIsAddonOrExpandedAddonPrincipal()) {
17330 return false;
17333 return !NodePrincipal()->SchemeIs("about") &&
17334 !NodePrincipal()->SchemeIs("chrome") &&
17335 !NodePrincipal()->SchemeIs("resource");
17338 void Document::GetConnectedShadowRoots(
17339 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
17340 AppendToArray(aOut, mComposedShadowRoots);
17343 bool Document::HasPictureInPictureChildElement() const {
17344 return mPictureInPictureChildElementCount > 0;
17347 void Document::EnableChildElementInPictureInPictureMode() {
17348 mPictureInPictureChildElementCount++;
17349 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
17352 void Document::DisableChildElementInPictureInPictureMode() {
17353 mPictureInPictureChildElementCount--;
17354 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
17357 void Document::AddMediaElementWithMSE() {
17358 if (mMediaElementWithMSECount++ == 0) {
17359 WindowGlobalChild* wgc = GetWindowGlobalChild();
17360 if (wgc) {
17361 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
17366 void Document::RemoveMediaElementWithMSE() {
17367 MOZ_ASSERT(mMediaElementWithMSECount > 0);
17368 if (--mMediaElementWithMSECount == 0) {
17369 WindowGlobalChild* wgc = GetWindowGlobalChild();
17370 if (wgc) {
17371 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
17376 void Document::UnregisterFromMemoryReportingForDataDocument() {
17377 if (!mAddedToMemoryReportingAsDataDocument) {
17378 return;
17380 mAddedToMemoryReportingAsDataDocument = false;
17381 nsIGlobalObject* global = GetScopeObject();
17382 if (global) {
17383 if (nsPIDOMWindowInner* win = global->AsInnerWindow()) {
17384 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
17385 this);
17389 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
17390 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
17391 mOOPChildrenLoading.AppendElement(aChild);
17392 if (mOOPChildrenLoading.Length() == 1) {
17393 // Let's block unload so that we're blocked from going into the BFCache
17394 // until the child has actually notified us that it has done loading.
17395 BlockOnload();
17399 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
17400 // aChild will not be in the list if nsDocLoader::Stop() was called, since
17401 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
17402 // so we don't need to call DocLoaderIsEmpty in that case.
17403 if (mOOPChildrenLoading.RemoveElement(aChild)) {
17404 if (mOOPChildrenLoading.IsEmpty()) {
17405 UnblockOnload(false);
17407 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
17408 if (docLoader) {
17409 docLoader->OOPChildrenLoadingIsEmpty();
17414 void Document::ClearOOPChildrenLoading() {
17415 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
17416 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
17417 if (!oopChildrenLoading.IsEmpty()) {
17418 UnblockOnload(false);
17422 } // namespace mozilla::dom