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/. */
6
7 /*
8 * Base class for all our document implementations.
9 */
10
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/DocumentInlines.h"
13
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/Maybe.h"
82 #include "mozilla/MediaFeatureChange.h"
83 #include "mozilla/MediaManager.h"
84 #include "mozilla/MemoryReporting.h"
85 #include "mozilla/NullPrincipal.h"
86 #include "mozilla/OriginAttributes.h"
87 #include "mozilla/OwningNonNull.h"
88 #include "mozilla/PendingAnimationTracker.h"
89 #include "mozilla/PendingFullscreenEvent.h"
90 #include "mozilla/PermissionDelegateHandler.h"
91 #include "mozilla/PermissionManager.h"
92 #include "mozilla/Preferences.h"
93 #include "mozilla/PreloadHashKey.h"
94 #include "mozilla/PresShell.h"
95 #include "mozilla/PresShellForwards.h"
96 #include "mozilla/PresShellInlines.h"
97 #include "mozilla/PseudoStyleType.h"
98 #include "mozilla/RefCountType.h"
99 #include "mozilla/RelativeTo.h"
100 #include "mozilla/RestyleManager.h"
101 #include "mozilla/ReverseIterator.h"
102 #include "mozilla/SMILAnimationController.h"
103 #include "mozilla/SMILTimeContainer.h"
104 #include "mozilla/ScopeExit.h"
105 #include "mozilla/Components.h"
106 #include "mozilla/ServoStyleConsts.h"
107 #include "mozilla/ServoStyleSet.h"
108 #include "mozilla/ServoTypes.h"
109 #include "mozilla/SizeOfState.h"
110 #include "mozilla/Span.h"
111 #include "mozilla/Sprintf.h"
112 #include "mozilla/StaticAnalysisFunctions.h"
113 #include "mozilla/StaticPrefs_apz.h"
114 #include "mozilla/StaticPrefs_browser.h"
115 #include "mozilla/StaticPrefs_docshell.h"
116 #include "mozilla/StaticPrefs_dom.h"
117 #include "mozilla/StaticPrefs_fission.h"
118 #include "mozilla/StaticPrefs_full_screen_api.h"
119 #include "mozilla/StaticPrefs_layout.h"
120 #include "mozilla/StaticPrefs_network.h"
121 #include "mozilla/StaticPrefs_page_load.h"
122 #include "mozilla/StaticPrefs_plugins.h"
123 #include "mozilla/StaticPrefs_privacy.h"
124 #include "mozilla/StaticPrefs_security.h"
125 #include "mozilla/StaticPrefs_widget.h"
126 #include "mozilla/StaticPresData.h"
127 #include "mozilla/StorageAccess.h"
128 #include "mozilla/StoragePrincipalHelper.h"
129 #include "mozilla/StyleSheet.h"
130 #include "mozilla/Telemetry.h"
131 #include "mozilla/TelemetryHistogramEnums.h"
132 #include "mozilla/TelemetryScalarEnums.h"
133 #include "mozilla/TextControlElement.h"
134 #include "mozilla/TextEditor.h"
135 #include "mozilla/TimelineConsumers.h"
136 #include "mozilla/TypedEnumBits.h"
137 #include "mozilla/URLDecorationStripper.h"
138 #include "mozilla/URLExtraData.h"
139 #include "mozilla/Unused.h"
140 #include "mozilla/css/ImageLoader.h"
141 #include "mozilla/css/Loader.h"
142 #include "mozilla/css/Rule.h"
143 #include "mozilla/css/SheetParsingMode.h"
144 #include "mozilla/dom/AnonymousContent.h"
145 #include "mozilla/dom/BrowserChild.h"
146 #include "mozilla/dom/BrowsingContext.h"
147 #include "mozilla/dom/BrowsingContextGroup.h"
148 #include "mozilla/dom/CDATASection.h"
149 #include "mozilla/dom/CSPDictionariesBinding.h"
150 #include "mozilla/dom/CanonicalBrowsingContext.h"
151 #include "mozilla/dom/ChromeObserver.h"
152 #include "mozilla/dom/ClientInfo.h"
153 #include "mozilla/dom/ClientState.h"
154 #include "mozilla/dom/Comment.h"
155 #include "mozilla/dom/ContentChild.h"
156 #include "mozilla/dom/DOMImplementation.h"
157 #include "mozilla/dom/DOMIntersectionObserver.h"
158 #include "mozilla/dom/DOMStringList.h"
159 #include "mozilla/dom/DocGroup.h"
160 #include "mozilla/dom/DocumentBinding.h"
161 #include "mozilla/dom/DocumentFragment.h"
162 #include "mozilla/dom/DocumentL10n.h"
163 #include "mozilla/dom/DocumentTimeline.h"
164 #include "mozilla/dom/DocumentType.h"
165 #include "mozilla/dom/ElementBinding.h"
166 #include "mozilla/dom/Event.h"
167 #include "mozilla/dom/EventListenerBinding.h"
168 #include "mozilla/dom/FailedCertSecurityInfoBinding.h"
169 #include "mozilla/dom/FeaturePolicy.h"
170 #include "mozilla/dom/FeaturePolicyUtils.h"
171 #include "mozilla/dom/FontFaceSet.h"
172 #include "mozilla/dom/FromParser.h"
173 #include "mozilla/dom/HTMLAllCollection.h"
174 #include "mozilla/dom/HTMLBodyElement.h"
175 #include "mozilla/dom/HTMLCollectionBinding.h"
176 #include "mozilla/dom/HTMLDialogElement.h"
177 #include "mozilla/dom/HTMLFormElement.h"
178 #include "mozilla/dom/HTMLIFrameElement.h"
179 #include "mozilla/dom/HTMLImageElement.h"
180 #include "mozilla/dom/HTMLInputElement.h"
181 #include "mozilla/dom/HTMLLinkElement.h"
182 #include "mozilla/dom/HTMLMediaElement.h"
183 #include "mozilla/dom/HTMLMetaElement.h"
184 #include "mozilla/dom/HTMLSharedElement.h"
185 #include "mozilla/dom/HTMLTextAreaElement.h"
186 #include "mozilla/dom/ImageTracker.h"
187 #include "mozilla/dom/Link.h"
188 #include "mozilla/dom/MediaQueryList.h"
189 #include "mozilla/dom/MediaSource.h"
190 #include "mozilla/dom/MutationObservers.h"
191 #include "mozilla/dom/NameSpaceConstants.h"
192 #include "mozilla/dom/Navigator.h"
193 #include "mozilla/dom/NetErrorInfoBinding.h"
194 #include "mozilla/dom/NodeInfo.h"
195 #include "mozilla/dom/NodeIterator.h"
196 #include "mozilla/dom/PContentChild.h"
197 #include "mozilla/dom/PWindowGlobalChild.h"
198 #include "mozilla/dom/PageTransitionEvent.h"
199 #include "mozilla/dom/PageTransitionEventBinding.h"
200 #include "mozilla/dom/Performance.h"
201 #include "mozilla/dom/PermissionMessageUtils.h"
202 #include "mozilla/dom/PostMessageEvent.h"
203 #include "mozilla/dom/ProcessingInstruction.h"
204 #include "mozilla/dom/Promise.h"
205 #include "mozilla/dom/PromiseNativeHandler.h"
206 #include "mozilla/dom/ResizeObserverController.h"
207 #include "mozilla/dom/SVGElement.h"
208 #include "mozilla/dom/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/TreeOrderedArrayInlines.h"
225 #include "mozilla/dom/TreeWalker.h"
226 #include "mozilla/dom/URL.h"
227 #include "mozilla/dom/UserActivation.h"
228 #include "mozilla/dom/WindowBinding.h"
229 #include "mozilla/dom/WindowContext.h"
230 #include "mozilla/dom/WindowGlobalChild.h"
231 #include "mozilla/dom/WindowProxyHolder.h"
232 #include "mozilla/dom/WorkerDocumentListener.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 "nsCSSRendering.h"
261 #include "nsCanvasFrame.h"
262 #include "nsCaseTreatment.h"
263 #include "nsCharsetSource.h"
264 #include "nsCommandManager.h"
265 #include "nsCommandParams.h"
266 #include "nsComponentManagerUtils.h"
267 #include "nsContentCreatorFunctions.h"
268 #include "nsContentList.h"
269 #include "nsContentPermissionHelper.h"
270 #include "nsContentSecurityUtils.h"
271 #include "nsContentUtils.h"
272 #include "nsCoord.h"
273 #include "nsCycleCollectionNoteChild.h"
274 #include "nsCycleCollectionTraversalCallback.h"
275 #include "nsDOMAttributeMap.h"
276 #include "nsDOMCaretPosition.h"
277 #include "nsDOMNavigationTiming.h"
278 #include "nsDOMString.h"
279 #include "nsDeviceContext.h"
280 #include "nsDocShell.h"
281 #include "nsError.h"
282 #include "nsEscape.h"
283 #include "nsFocusManager.h"
284 #include "nsFrameLoader.h"
285 #include "nsFrameLoaderOwner.h"
286 #include "nsGenericHTMLElement.h"
287 #include "nsGlobalWindowInner.h"
288 #include "nsGlobalWindowOuter.h"
289 #include "nsHTMLCSSStyleSheet.h"
290 #include "nsHTMLDocument.h"
291 #include "nsHTMLStyleSheet.h"
292 #include "nsHtml5Module.h"
293 #include "nsHtml5Parser.h"
294 #include "nsHtml5TreeOpExecutor.h"
295 #include "nsIAsyncShutdown.h"
296 #include "nsIAuthPrompt.h"
297 #include "nsIAuthPrompt2.h"
298 #include "nsIBFCacheEntry.h"
299 #include "nsIBaseWindow.h"
300 #include "nsIBrowserChild.h"
301 #include "nsIBrowserUsage.h"
302 #include "nsICSSLoaderObserver.h"
303 #include "nsICategoryManager.h"
304 #include "nsICertOverrideService.h"
305 #include "nsIContent.h"
306 #include "nsIContentInlines.h"
307 #include "nsIContentPolicy.h"
308 #include "nsIContentSecurityPolicy.h"
309 #include "nsIContentSink.h"
310 #include "nsICookieJarSettings.h"
311 #include "nsICookieService.h"
312 #include "nsIDOMXULCommandDispatcher.h"
313 #include "nsIDocShell.h"
314 #include "nsIDocShellTreeItem.h"
315 #include "nsIDocumentActivity.h"
316 #include "nsIDocumentEncoder.h"
317 #include "nsIDocumentLoader.h"
318 #include "nsIDocumentLoaderFactory.h"
319 #include "nsIDocumentObserver.h"
320 #include "nsIEditingSession.h"
321 #include "nsIEditor.h"
322 #include "nsIEffectiveTLDService.h"
323 #include "nsIFile.h"
324 #include "nsIFileChannel.h"
325 #include "nsIFrame.h"
326 #include "nsIGlobalObject.h"
327 #include "nsIHTMLCollection.h"
328 #include "nsIHttpChannel.h"
329 #include "nsIHttpChannelInternal.h"
330 #include "nsIIOService.h"
331 #include "nsIImageLoadingContent.h"
332 #include "nsIInlineSpellChecker.h"
333 #include "nsIInputStreamChannel.h"
334 #include "nsIInterfaceRequestorUtils.h"
335 #include "nsILayoutHistoryState.h"
336 #include "nsIMultiPartChannel.h"
337 #include "nsIMutationObserver.h"
338 #include "nsINSSErrorsService.h"
339 #include "nsINamed.h"
340 #include "nsINodeList.h"
341 #include "nsIObjectLoadingContent.h"
342 #include "nsIObserverService.h"
343 #include "nsIPermission.h"
344 #include "nsIPrompt.h"
345 #include "nsIPropertyBag2.h"
346 #include "nsIPublicKeyPinningService.h"
347 #include "nsIReferrerInfo.h"
348 #include "nsIRefreshURI.h"
349 #include "nsIRequest.h"
350 #include "nsIRequestContext.h"
351 #include "nsIRunnable.h"
352 #include "nsISHEntry.h"
353 #include "nsIScriptElement.h"
354 #include "nsIScriptError.h"
355 #include "nsIScriptGlobalObject.h"
356 #include "nsIScriptSecurityManager.h"
357 #include "nsISecurityConsoleMessage.h"
358 #include "nsISelectionController.h"
359 #include "nsISerialEventTarget.h"
360 #include "nsISerializable.h"
361 #include "nsISimpleEnumerator.h"
362 #include "nsISiteSecurityService.h"
363 #include "nsISocketProvider.h"
364 #include "nsISpeculativeConnect.h"
365 #include "nsIStructuredCloneContainer.h"
366 #include "nsIThread.h"
367 #include "nsITimedChannel.h"
368 #include "nsITimer.h"
369 #include "nsITransportSecurityInfo.h"
370 #include "nsIURIMutator.h"
371 #include "nsIVariant.h"
372 #include "nsIWeakReference.h"
373 #include "nsIWebNavigation.h"
374 #include "nsIWidget.h"
375 #include "nsIX509Cert.h"
376 #include "nsIX509CertValidity.h"
377 #include "nsIXMLContentSink.h"
378 #include "nsIXULRuntime.h"
379 #include "nsImageLoadingContent.h"
380 #include "nsImportModule.h"
381 #include "nsLanguageAtomService.h"
382 #include "nsLayoutUtils.h"
383 #include "nsNetCID.h"
384 #include "nsNetUtil.h"
385 #include "nsNodeInfoManager.h"
386 #include "nsObjectLoadingContent.h"
387 #include "nsPIDOMWindowInlines.h"
388 #include "nsPIWindowRoot.h"
389 #include "nsPoint.h"
390 #include "nsPointerHashKeys.h"
391 #include "nsPresContext.h"
392 #include "nsQueryFrame.h"
393 #include "nsQueryObject.h"
394 #include "nsRange.h"
395 #include "nsRect.h"
396 #include "nsRefreshDriver.h"
397 #include "nsSandboxFlags.h"
398 #include "nsSerializationHelper.h"
399 #include "nsServiceManagerUtils.h"
400 #include "nsStringFlags.h"
401 #include "nsStyleUtil.h"
402 #include "nsStringIterator.h"
403 #include "nsStyleSheetService.h"
404 #include "nsStyleStruct.h"
405 #include "nsTextNode.h"
406 #include "nsUnicharUtils.h"
407 #include "nsWrapperCache.h"
408 #include "nsWrapperCacheInlines.h"
409 #include "nsXPCOMCID.h"
410 #include "nsXULAppAPI.h"
411 #include "prthread.h"
412 #include "prtime.h"
413 #include "prtypes.h"
414 #include "xpcpublic.h"
415
416 // XXX Must be included after mozilla/Encoding.h
417 #include "encoding_rs.h"
418
419 #include "mozilla/dom/XULBroadcastManager.h"
420 #include "mozilla/dom/XULPersist.h"
421 #include "nsIAppWindow.h"
422 #include "nsXULPrototypeDocument.h"
423 #include "nsXULCommandDispatcher.h"
424 #include "nsXULPopupManager.h"
425 #include "nsIDocShellTreeOwner.h"
426
427 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
428 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
429 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
430 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
431
432 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20
433
434 mozilla::LazyLogModule gPageCacheLog("PageCache");
435 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache");
436 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer");
437 mozilla::LazyLogModule gUseCountersLog("UseCounters");
438
439 namespace mozilla {
440 namespace dom {
441
442 class Document::HeaderData {
443 public:
HeaderData(nsAtom * aField,const nsAString & aData)444 HeaderData(nsAtom* aField, const nsAString& aData)
445 : mField(aField), mData(aData) {}
446
~HeaderData()447 ~HeaderData() {
448 // Delete iteratively to avoid blowing up the stack, though it shouldn't
449 // happen in practice.
450 UniquePtr<HeaderData> next = std::move(mNext);
451 while (next) {
452 next = std::move(next->mNext);
453 }
454 }
455
456 RefPtr<nsAtom> mField;
457 nsString mData;
458 UniquePtr<HeaderData> mNext;
459 };
460
461 using LinkArray = nsTArray<Link*>;
462
463 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument =
464 nullptr;
465
466 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
467 static LazyLogModule gCspPRLog("CSP");
468 LazyLogModule gUserInteractionPRLog("UserInteraction");
469
GetHttpChannelHelper(nsIChannel * aChannel,nsIHttpChannel ** aHttpChannel)470 static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
471 nsIHttpChannel** aHttpChannel) {
472 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
473 if (httpChannel) {
474 httpChannel.forget(aHttpChannel);
475 return NS_OK;
476 }
477
478 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
479 if (!multipart) {
480 *aHttpChannel = nullptr;
481 return NS_OK;
482 }
483
484 nsCOMPtr<nsIChannel> baseChannel;
485 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
486 if (NS_WARN_IF(NS_FAILED(rv))) {
487 return rv;
488 }
489
490 httpChannel = do_QueryInterface(baseChannel);
491 httpChannel.forget(aHttpChannel);
492
493 return NS_OK;
494 }
495
496 } // namespace dom
497
498 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
499
IdentifierMapEntry(const IdentifierMapEntry::DependentAtomOrString * aKey)500 IdentifierMapEntry::IdentifierMapEntry(
501 const IdentifierMapEntry::DependentAtomOrString* aKey)
502 : mKey(aKey ? *aKey : nullptr) {}
503
Traverse(nsCycleCollectionTraversalCallback * aCallback)504 void IdentifierMapEntry::Traverse(
505 nsCycleCollectionTraversalCallback* aCallback) {
506 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
507 "mIdentifierMap mNameContentList");
508 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList));
509
510 if (mImageElement) {
511 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
512 "mIdentifierMap mImageElement element");
513 nsIContent* imageElement = mImageElement;
514 aCallback->NoteXPCOMChild(imageElement);
515 }
516 }
517
IsEmpty()518 bool IdentifierMapEntry::IsEmpty() {
519 return mIdContentList->IsEmpty() && !mNameContentList && !mChangeCallbacks &&
520 !mImageElement;
521 }
522
HasNameElement() const523 bool IdentifierMapEntry::HasNameElement() const {
524 return mNameContentList && mNameContentList->Length() != 0;
525 }
526
AddContentChangeCallback(Document::IDTargetObserver aCallback,void * aData,bool aForImage)527 void IdentifierMapEntry::AddContentChangeCallback(
528 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
529 if (!mChangeCallbacks) {
530 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>();
531 }
532
533 ChangeCallback cc = {aCallback, aData, aForImage};
534 mChangeCallbacks->PutEntry(cc);
535 }
536
RemoveContentChangeCallback(Document::IDTargetObserver aCallback,void * aData,bool aForImage)537 void IdentifierMapEntry::RemoveContentChangeCallback(
538 Document::IDTargetObserver aCallback, void* aData, bool aForImage) {
539 if (!mChangeCallbacks) return;
540 ChangeCallback cc = {aCallback, aData, aForImage};
541 mChangeCallbacks->RemoveEntry(cc);
542 if (mChangeCallbacks->Count() == 0) {
543 mChangeCallbacks = nullptr;
544 }
545 }
546
FireChangeCallbacks(Element * aOldElement,Element * aNewElement,bool aImageOnly)547 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
548 Element* aNewElement,
549 bool aImageOnly) {
550 if (!mChangeCallbacks) return;
551
552 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) {
553 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get();
554 // Don't fire image changes for non-image observers, and don't fire element
555 // changes for image observers when an image override is active.
556 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) {
557 continue;
558 }
559
560 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) {
561 iter.Remove();
562 }
563 }
564 }
565
AddIdElement(Element * aElement)566 void IdentifierMapEntry::AddIdElement(Element* aElement) {
567 MOZ_ASSERT(aElement, "Must have element");
568 MOZ_ASSERT(!mIdContentList->Contains(nullptr), "Why is null in our list?");
569
570 size_t index = mIdContentList.Insert(*aElement);
571 if (index == 0) {
572 Element* oldElement = mIdContentList->SafeElementAt(1);
573 FireChangeCallbacks(oldElement, aElement);
574 }
575 }
576
RemoveIdElement(Element * aElement)577 void IdentifierMapEntry::RemoveIdElement(Element* aElement) {
578 MOZ_ASSERT(aElement, "Missing element");
579
580 // This should only be called while the document is in an update.
581 // Assertions near the call to this method guarantee this.
582
583 // This could fire in OOM situations
584 // Only assert this in HTML documents for now as XUL does all sorts of weird
585 // crap.
586 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() ||
587 mIdContentList->Contains(aElement),
588 "Removing id entry that doesn't exist");
589
590 // XXXbz should this ever Compact() I guess when all the content is gone
591 // we'll just get cleaned up in the natural order of things...
592 Element* currentElement = mIdContentList->SafeElementAt(0);
593 mIdContentList.RemoveElement(*aElement);
594 if (currentElement == aElement) {
595 FireChangeCallbacks(currentElement, mIdContentList->SafeElementAt(0));
596 }
597 }
598
SetImageElement(Element * aElement)599 void IdentifierMapEntry::SetImageElement(Element* aElement) {
600 Element* oldElement = GetImageIdElement();
601 mImageElement = aElement;
602 Element* newElement = GetImageIdElement();
603 if (oldElement != newElement) {
604 FireChangeCallbacks(oldElement, newElement, true);
605 }
606 }
607
ClearAndNotify()608 void IdentifierMapEntry::ClearAndNotify() {
609 Element* currentElement = mIdContentList->SafeElementAt(0);
610 mIdContentList.Clear();
611 if (currentElement) {
612 FireChangeCallbacks(currentElement, nullptr);
613 }
614 mNameContentList = nullptr;
615 if (mImageElement) {
616 SetImageElement(nullptr);
617 }
618 mChangeCallbacks = nullptr;
619 }
620
621 namespace dom {
622
623 class SimpleHTMLCollection final : public nsSimpleContentList,
624 public nsIHTMLCollection {
625 public:
SimpleHTMLCollection(nsINode * aRoot)626 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {}
627
628 NS_DECL_ISUPPORTS_INHERITED
629
GetParentObject()630 virtual nsINode* GetParentObject() override {
631 return nsSimpleContentList::GetParentObject();
632 }
Length()633 virtual uint32_t Length() override { return nsSimpleContentList::Length(); }
GetElementAt(uint32_t aIndex)634 virtual Element* GetElementAt(uint32_t aIndex) override {
635 return mElements.SafeElementAt(aIndex)->AsElement();
636 }
637
GetFirstNamedElement(const nsAString & aName,bool & aFound)638 virtual Element* GetFirstNamedElement(const nsAString& aName,
639 bool& aFound) override {
640 aFound = false;
641 RefPtr<nsAtom> name = NS_Atomize(aName);
642 for (uint32_t i = 0; i < mElements.Length(); i++) {
643 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
644 Element* element = mElements[i]->AsElement();
645 if (element->GetID() == name ||
646 (element->HasName() &&
647 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) {
648 aFound = true;
649 return element;
650 }
651 }
652 return nullptr;
653 }
654
GetSupportedNames(nsTArray<nsString> & aNames)655 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override {
656 AutoTArray<nsAtom*, 8> atoms;
657 for (uint32_t i = 0; i < mElements.Length(); i++) {
658 MOZ_DIAGNOSTIC_ASSERT(mElements[i]);
659 Element* element = mElements[i]->AsElement();
660
661 nsAtom* id = element->GetID();
662 MOZ_ASSERT(id != nsGkAtoms::_empty);
663 if (id && !atoms.Contains(id)) {
664 atoms.AppendElement(id);
665 }
666
667 if (element->HasName()) {
668 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
669 MOZ_ASSERT(name && name != nsGkAtoms::_empty);
670 if (name && !atoms.Contains(name)) {
671 atoms.AppendElement(name);
672 }
673 }
674 }
675
676 nsString* names = aNames.AppendElements(atoms.Length());
677 for (uint32_t i = 0; i < atoms.Length(); i++) {
678 atoms[i]->ToString(names[i]);
679 }
680 }
681
GetWrapperPreserveColorInternal()682 virtual JSObject* GetWrapperPreserveColorInternal() override {
683 return nsWrapperCache::GetWrapperPreserveColor();
684 }
PreserveWrapperInternal(nsISupports * aScriptObjectHolder)685 virtual void PreserveWrapperInternal(
686 nsISupports* aScriptObjectHolder) override {
687 nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
688 }
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)689 virtual JSObject* WrapObject(JSContext* aCx,
690 JS::Handle<JSObject*> aGivenProto) override {
691 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto);
692 }
693
694 using nsBaseContentList::Item;
695
696 private:
697 virtual ~SimpleHTMLCollection() = default;
698 };
699
700 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList,
701 nsIHTMLCollection)
702
703 } // namespace dom
704
AddNameElement(nsINode * aNode,Element * aElement)705 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) {
706 if (!mNameContentList) {
707 mNameContentList = new dom::SimpleHTMLCollection(aNode);
708 }
709
710 mNameContentList->AppendElement(aElement);
711 }
712
RemoveNameElement(Element * aElement)713 void IdentifierMapEntry::RemoveNameElement(Element* aElement) {
714 if (mNameContentList) {
715 mNameContentList->RemoveElement(aElement);
716 }
717 }
718
HasIdElementExposedAsHTMLDocumentProperty() const719 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const {
720 Element* idElement = GetIdElement();
721 return idElement &&
722 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
723 }
724
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const725 size_t IdentifierMapEntry::SizeOfExcludingThis(
726 MallocSizeOf aMallocSizeOf) const {
727 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
728 }
729
730 // Helper structs for the content->subdoc map
731
732 class SubDocMapEntry : public PLDHashEntryHdr {
733 public:
734 // Both of these are strong references
735 dom::Element* mKey; // must be first, to look like PLDHashEntryStub
736 dom::Document* mSubDocument;
737 };
738
739 class OnloadBlocker final : public nsIRequest {
740 public:
741 OnloadBlocker() = default;
742
743 NS_DECL_ISUPPORTS
744 NS_DECL_NSIREQUEST
745
746 private:
747 ~OnloadBlocker() = default;
748 };
749
NS_IMPL_ISUPPORTS(OnloadBlocker,nsIRequest)750 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest)
751
752 NS_IMETHODIMP
753 OnloadBlocker::GetName(nsACString& aResult) {
754 aResult.AssignLiteral("about:document-onload-blocker");
755 return NS_OK;
756 }
757
758 NS_IMETHODIMP
IsPending(bool * _retval)759 OnloadBlocker::IsPending(bool* _retval) {
760 *_retval = true;
761 return NS_OK;
762 }
763
764 NS_IMETHODIMP
GetStatus(nsresult * status)765 OnloadBlocker::GetStatus(nsresult* status) {
766 *status = NS_OK;
767 return NS_OK;
768 }
769
770 NS_IMETHODIMP
Cancel(nsresult status)771 OnloadBlocker::Cancel(nsresult status) { return NS_OK; }
772 NS_IMETHODIMP
Suspend(void)773 OnloadBlocker::Suspend(void) { return NS_OK; }
774 NS_IMETHODIMP
Resume(void)775 OnloadBlocker::Resume(void) { return NS_OK; }
776
777 NS_IMETHODIMP
GetLoadGroup(nsILoadGroup ** aLoadGroup)778 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) {
779 *aLoadGroup = nullptr;
780 return NS_OK;
781 }
782
783 NS_IMETHODIMP
SetLoadGroup(nsILoadGroup * aLoadGroup)784 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
785
786 NS_IMETHODIMP
GetLoadFlags(nsLoadFlags * aLoadFlags)787 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) {
788 *aLoadFlags = nsIRequest::LOAD_NORMAL;
789 return NS_OK;
790 }
791
792 NS_IMETHODIMP
GetTRRMode(nsIRequest::TRRMode * aTRRMode)793 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
794 return GetTRRModeImpl(aTRRMode);
795 }
796
797 NS_IMETHODIMP
SetTRRMode(nsIRequest::TRRMode aTRRMode)798 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
799 return SetTRRModeImpl(aTRRMode);
800 }
801
802 NS_IMETHODIMP
SetLoadFlags(nsLoadFlags aLoadFlags)803 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
804
805 // ==================================================================
806
807 namespace dom {
808
ExternalResourceMap()809 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {}
810
RequestResource(nsIURI * aURI,nsIReferrerInfo * aReferrerInfo,nsINode * aRequestingNode,Document * aDisplayDocument,ExternalResourceLoad ** aPendingLoad)811 Document* ExternalResourceMap::RequestResource(
812 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
813 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) {
814 // If we ever start allowing non-same-origin loads here, we might need to do
815 // something interesting with aRequestingPrincipal even for the hashtable
816 // gets.
817 MOZ_ASSERT(aURI, "Must have a URI");
818 MOZ_ASSERT(aRequestingNode, "Must have a node");
819 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
820 *aPendingLoad = nullptr;
821 if (mHaveShutDown) {
822 return nullptr;
823 }
824
825 // First, make sure we strip the ref from aURI.
826 nsCOMPtr<nsIURI> clone;
827 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone));
828 if (NS_FAILED(rv) || !clone) {
829 return nullptr;
830 }
831
832 ExternalResource* resource;
833 mMap.Get(clone, &resource);
834 if (resource) {
835 return resource->mDocument;
836 }
837
838 bool loadStartSucceeded =
839 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) {
840 if (!loadEntry) {
841 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument));
842
843 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo,
844 aRequestingNode))) {
845 return false;
846 }
847 }
848
849 RefPtr<PendingLoad> load(loadEntry.Data());
850 load.forget(aPendingLoad);
851 return true;
852 });
853 if (!loadStartSucceeded) {
854 // Make sure we don't thrash things by trying this load again, since
855 // chances are it failed for good reasons (security check, etc).
856 // This must be done outside the WithEntryHandle functor, as it accesses
857 // mPendingLoads.
858 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
859 }
860
861 return nullptr;
862 }
863
EnumerateResources(SubDocEnumFunc aCallback)864 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) {
865 nsTArray<RefPtr<Document>> docs(mMap.Count());
866 for (const auto& entry : mMap.Values()) {
867 if (Document* doc = entry->mDocument) {
868 docs.AppendElement(doc);
869 }
870 }
871
872 for (auto& doc : docs) {
873 if (aCallback(*doc) == CallState::Stop) {
874 return;
875 }
876 }
877 }
878
Traverse(nsCycleCollectionTraversalCallback * aCallback) const879 void ExternalResourceMap::Traverse(
880 nsCycleCollectionTraversalCallback* aCallback) const {
881 // mPendingLoads will get cleared out as the requests complete, so
882 // no need to worry about those here.
883 for (const auto& entry : mMap) {
884 ExternalResourceMap::ExternalResource* resource = entry.GetWeak();
885
886 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
887 "mExternalResourceMap.mMap entry"
888 "->mDocument");
889 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument));
890
891 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
892 "mExternalResourceMap.mMap entry"
893 "->mViewer");
894 aCallback->NoteXPCOMChild(resource->mViewer);
895
896 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
897 "mExternalResourceMap.mMap entry"
898 "->mLoadGroup");
899 aCallback->NoteXPCOMChild(resource->mLoadGroup);
900 }
901 }
902
HideViewers()903 void ExternalResourceMap::HideViewers() {
904 for (const auto& entry : mMap) {
905 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
906 if (viewer) {
907 viewer->Hide();
908 }
909 }
910 }
911
ShowViewers()912 void ExternalResourceMap::ShowViewers() {
913 for (const auto& entry : mMap) {
914 nsCOMPtr<nsIContentViewer> viewer = entry.GetData()->mViewer;
915 if (viewer) {
916 viewer->Show();
917 }
918 }
919 }
920
TransferShowingState(Document * aFromDoc,Document * aToDoc)921 void TransferShowingState(Document* aFromDoc, Document* aToDoc) {
922 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc");
923
924 if (aFromDoc->IsShowing()) {
925 aToDoc->OnPageShow(true, nullptr);
926 }
927 }
928
AddExternalResource(nsIURI * aURI,nsIContentViewer * aViewer,nsILoadGroup * aLoadGroup,Document * aDisplayDocument)929 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI,
930 nsIContentViewer* aViewer,
931 nsILoadGroup* aLoadGroup,
932 Document* aDisplayDocument) {
933 MOZ_ASSERT(aURI, "Unexpected call");
934 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
935 "Must have both or neither");
936
937 RefPtr<PendingLoad> load;
938 mPendingLoads.Remove(aURI, getter_AddRefs(load));
939
940 nsresult rv = NS_OK;
941
942 nsCOMPtr<Document> doc;
943 if (aViewer) {
944 doc = aViewer->GetDocument();
945 NS_ASSERTION(doc, "Must have a document");
946
947 doc->SetDisplayDocument(aDisplayDocument);
948
949 // Make sure that hiding our viewer will tear down its presentation.
950 aViewer->SetSticky(false);
951
952 rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
953 if (NS_SUCCEEDED(rv)) {
954 rv = aViewer->Open(nullptr, nullptr);
955 }
956
957 if (NS_FAILED(rv)) {
958 doc = nullptr;
959 aViewer = nullptr;
960 aLoadGroup = nullptr;
961 }
962 }
963
964 ExternalResource* newResource =
965 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get();
966
967 newResource->mDocument = doc;
968 newResource->mViewer = aViewer;
969 newResource->mLoadGroup = aLoadGroup;
970 if (doc) {
971 if (nsPresContext* pc = doc->GetPresContext()) {
972 pc->RecomputeBrowsingContextDependentData();
973 }
974 TransferShowingState(aDisplayDocument, doc);
975 }
976
977 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers();
978 for (uint32_t i = 0; i < obs.Length(); ++i) {
979 obs[i]->Observe(ToSupports(doc), "external-resource-document-created",
980 nullptr);
981 }
982
983 return rv;
984 }
985
NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad,nsIStreamListener,nsIRequestObserver)986 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener,
987 nsIRequestObserver)
988
989 NS_IMETHODIMP
990 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) {
991 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap();
992 if (map.HaveShutDown()) {
993 return NS_BINDING_ABORTED;
994 }
995
996 nsCOMPtr<nsIContentViewer> viewer;
997 nsCOMPtr<nsILoadGroup> loadGroup;
998 nsresult rv =
999 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup));
1000
1001 // Make sure to do this no matter what
1002 nsresult rv2 =
1003 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument);
1004 if (NS_FAILED(rv)) {
1005 return rv;
1006 }
1007 if (NS_FAILED(rv2)) {
1008 mTargetListener = nullptr;
1009 return rv2;
1010 }
1011
1012 return mTargetListener->OnStartRequest(aRequest);
1013 }
1014
SetupViewer(nsIRequest * aRequest,nsIContentViewer ** aViewer,nsILoadGroup ** aLoadGroup)1015 nsresult ExternalResourceMap::PendingLoad::SetupViewer(
1016 nsIRequest* aRequest, nsIContentViewer** aViewer,
1017 nsILoadGroup** aLoadGroup) {
1018 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest");
1019 *aViewer = nullptr;
1020 *aLoadGroup = nullptr;
1021
1022 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1023 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
1024
1025 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1026 if (httpChannel) {
1027 bool requestSucceeded;
1028 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
1029 !requestSucceeded) {
1030 // Bail out on this load, since it looks like we have an HTTP error page
1031 return NS_BINDING_ABORTED;
1032 }
1033 }
1034
1035 nsAutoCString type;
1036 chan->GetContentType(type);
1037
1038 nsCOMPtr<nsILoadGroup> loadGroup;
1039 chan->GetLoadGroup(getter_AddRefs(loadGroup));
1040
1041 // Give this document its own loadgroup
1042 nsCOMPtr<nsILoadGroup> newLoadGroup =
1043 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
1044 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
1045 newLoadGroup->SetLoadGroup(loadGroup);
1046
1047 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1048 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1049
1050 nsCOMPtr<nsIInterfaceRequestor> newCallbacks =
1051 new LoadgroupCallbacks(callbacks);
1052 newLoadGroup->SetNotificationCallbacks(newCallbacks);
1053
1054 // This is some serious hackery cribbed from docshell
1055 nsCOMPtr<nsICategoryManager> catMan =
1056 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
1057 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
1058 nsCString contractId;
1059 nsresult rv =
1060 catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
1061 NS_ENSURE_SUCCESS(rv, rv);
1062 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
1063 do_GetService(contractId.get());
1064 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
1065
1066 nsCOMPtr<nsIContentViewer> viewer;
1067 nsCOMPtr<nsIStreamListener> listener;
1068 rv = docLoaderFactory->CreateInstance(
1069 "external-resource", chan, newLoadGroup, type, nullptr, nullptr,
1070 getter_AddRefs(listener), getter_AddRefs(viewer));
1071 NS_ENSURE_SUCCESS(rv, rv);
1072 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
1073
1074 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
1075 if (!parser) {
1076 /// We don't want to deal with the various fake documents yet
1077 return NS_ERROR_NOT_IMPLEMENTED;
1078 }
1079
1080 // We can't handle HTML and other weird things here yet.
1081 nsIContentSink* sink = parser->GetContentSink();
1082 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink);
1083 if (!xmlSink) {
1084 return NS_ERROR_NOT_IMPLEMENTED;
1085 }
1086
1087 listener.swap(mTargetListener);
1088 viewer.forget(aViewer);
1089 newLoadGroup.forget(aLoadGroup);
1090 return NS_OK;
1091 }
1092
1093 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsIInputStream * aStream,uint64_t aOffset,uint32_t aCount)1094 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
1095 nsIInputStream* aStream,
1096 uint64_t aOffset,
1097 uint32_t aCount) {
1098 // mTargetListener might be null if SetupViewer or AddExternalResource failed.
1099 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE);
1100 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
1101 return NS_BINDING_ABORTED;
1102 }
1103 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1104 }
1105
1106 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult aStatus)1107 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
1108 nsresult aStatus) {
1109 // mTargetListener might be null if SetupViewer or AddExternalResource failed
1110 if (mTargetListener) {
1111 nsCOMPtr<nsIStreamListener> listener;
1112 mTargetListener.swap(listener);
1113 return listener->OnStopRequest(aRequest, aStatus);
1114 }
1115
1116 return NS_OK;
1117 }
1118
StartLoad(nsIURI * aURI,nsIReferrerInfo * aReferrerInfo,nsINode * aRequestingNode)1119 nsresult ExternalResourceMap::PendingLoad::StartLoad(
1120 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) {
1121 MOZ_ASSERT(aURI, "Must have a URI");
1122 MOZ_ASSERT(aRequestingNode, "Must have a node");
1123 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
1124
1125 nsCOMPtr<nsILoadGroup> loadGroup =
1126 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
1127
1128 nsresult rv = NS_OK;
1129 nsCOMPtr<nsIChannel> channel;
1130 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode,
1131 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
1132 nsIContentPolicy::TYPE_OTHER,
1133 nullptr, // aPerformanceStorage
1134 loadGroup);
1135 NS_ENSURE_SUCCESS(rv, rv);
1136
1137 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1138 if (httpChannel) {
1139 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
1140 Unused << NS_WARN_IF(NS_FAILED(rv));
1141 }
1142
1143 mURI = aURI;
1144
1145 return channel->AsyncOpen(this);
1146 }
1147
NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,nsIInterfaceRequestor)1148 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks,
1149 nsIInterfaceRequestor)
1150
1151 #define IMPL_SHIM(_i) \
1152 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
1153
1154 IMPL_SHIM(nsILoadContext)
1155 IMPL_SHIM(nsIProgressEventSink)
1156 IMPL_SHIM(nsIChannelEventSink)
1157
1158 #undef IMPL_SHIM
1159
1160 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i))
1161
1162 #define TRY_SHIM(_i) \
1163 PR_BEGIN_MACRO \
1164 if (IID_IS(_i)) { \
1165 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \
1166 if (!real) { \
1167 return NS_NOINTERFACE; \
1168 } \
1169 nsCOMPtr<_i> shim = new _i##Shim(this, real); \
1170 shim.forget(aSink); \
1171 return NS_OK; \
1172 } \
1173 PR_END_MACRO
1174
1175 NS_IMETHODIMP
1176 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID,
1177 void** aSink) {
1178 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) ||
1179 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) {
1180 return mCallbacks->GetInterface(aIID, aSink);
1181 }
1182
1183 *aSink = nullptr;
1184
1185 TRY_SHIM(nsILoadContext);
1186 TRY_SHIM(nsIProgressEventSink);
1187 TRY_SHIM(nsIChannelEventSink);
1188
1189 return NS_NOINTERFACE;
1190 }
1191
1192 #undef TRY_SHIM
1193 #undef IID_IS
1194
~ExternalResource()1195 ExternalResourceMap::ExternalResource::~ExternalResource() {
1196 if (mViewer) {
1197 mViewer->Close(nullptr);
1198 mViewer->Destroy();
1199 }
1200 }
1201
1202 // ==================================================================
1203 // =
1204 // ==================================================================
1205
1206 // If we ever have an nsIDocumentObserver notification for stylesheet title
1207 // changes we should update the list from that instead of overriding
1208 // EnsureFresh.
1209 class DOMStyleSheetSetList final : public DOMStringList {
1210 public:
1211 explicit DOMStyleSheetSetList(Document* aDocument);
1212
Disconnect()1213 void Disconnect() { mDocument = nullptr; }
1214
1215 virtual void EnsureFresh() override;
1216
1217 protected:
1218 Document* mDocument; // Our document; weak ref. It'll let us know if it
1219 // dies.
1220 };
1221
DOMStyleSheetSetList(Document * aDocument)1222 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument)
1223 : mDocument(aDocument) {
1224 NS_ASSERTION(mDocument, "Must have document!");
1225 }
1226
EnsureFresh()1227 void DOMStyleSheetSetList::EnsureFresh() {
1228 MOZ_ASSERT(NS_IsMainThread());
1229
1230 mNames.Clear();
1231
1232 if (!mDocument) {
1233 return; // Spec says "no exceptions", and we have no style sets if we have
1234 // no document, for sure
1235 }
1236
1237 size_t count = mDocument->SheetCount();
1238 nsAutoString title;
1239 for (size_t index = 0; index < count; index++) {
1240 StyleSheet* sheet = mDocument->SheetAt(index);
1241 NS_ASSERTION(sheet, "Null sheet in sheet list!");
1242 sheet->GetTitle(title);
1243 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
1244 return;
1245 }
1246 }
1247 }
1248
1249 // ==================================================================
SelectorCache(nsIEventTarget * aEventTarget)1250 Document::SelectorCache::SelectorCache(nsIEventTarget* aEventTarget)
1251 : nsExpirationTracker<SelectorCacheKey, 4>(1000, "Document::SelectorCache",
1252 aEventTarget) {}
1253
~SelectorCache()1254 Document::SelectorCache::~SelectorCache() { AgeAllGenerations(); }
1255
NotifyExpired(SelectorCacheKey * aSelector)1256 void Document::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector) {
1257 MOZ_ASSERT(NS_IsMainThread());
1258 MOZ_ASSERT(aSelector);
1259
1260 // There is no guarantee that this method won't be re-entered when selector
1261 // matching is ongoing because "memory-pressure" could be notified immediately
1262 // when OOM happens according to the design of nsExpirationTracker.
1263 // The perfect solution is to delete the |aSelector| and its
1264 // RawServoSelectorList in mTable asynchronously.
1265 // We remove these objects synchronously for now because NotifyExpired() will
1266 // never be triggered by "memory-pressure" which is not implemented yet in
1267 // the stage 2 of mozalloc_handle_oom().
1268 // Once these objects are removed asynchronously, we should update the warning
1269 // added in mozalloc_handle_oom() as well.
1270 RemoveObject(aSelector);
1271 mTable.Remove(aSelector->mKey);
1272 delete aSelector;
1273 }
1274
1275 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default;
1276
1277 // ==================================================================
1278 // =
1279 // ==================================================================
1280
1281 Document::InternalCommandDataHashtable*
1282 Document::sInternalCommandDataHashtable = nullptr;
1283
1284 // static
Shutdown()1285 void Document::Shutdown() {
1286 if (sInternalCommandDataHashtable) {
1287 sInternalCommandDataHashtable->Clear();
1288 delete sInternalCommandDataHashtable;
1289 sInternalCommandDataHashtable = nullptr;
1290 }
1291 }
1292
Document(const char * aContentType)1293 Document::Document(const char* aContentType)
1294 : nsINode(nullptr),
1295 DocumentOrShadowRoot(this),
1296 mCharacterSet(WINDOWS_1252_ENCODING),
1297 mCharacterSetSource(0),
1298 mParentDocument(nullptr),
1299 mCachedRootElement(nullptr),
1300 mNodeInfoManager(nullptr),
1301 #ifdef DEBUG
1302 mStyledLinksCleared(false),
1303 #endif
1304 mBlockAllMixedContent(false),
1305 mBlockAllMixedContentPreloads(false),
1306 mUpgradeInsecureRequests(false),
1307 mUpgradeInsecurePreloads(false),
1308 mDevToolsWatchingDOMMutations(false),
1309 mBidiEnabled(false),
1310 mMayNeedFontPrefsUpdate(true),
1311 mMathMLEnabled(false),
1312 mIsInitialDocumentInWindow(false),
1313 mIgnoreDocGroupMismatches(false),
1314 mLoadedAsData(false),
1315 mAddedToMemoryReportingAsDataDocument(false),
1316 mMayStartLayout(true),
1317 mHaveFiredTitleChange(false),
1318 mIsShowing(false),
1319 mVisible(true),
1320 mRemovedFromDocShell(false),
1321 // mAllowDNSPrefetch starts true, so that we can always reliably && it
1322 // with various values that might disable it. Since we never prefetch
1323 // unless we get a window, and in that case the docshell value will get
1324 // &&-ed in, this is safe.
1325 mAllowDNSPrefetch(true),
1326 mIsStaticDocument(false),
1327 mCreatingStaticClone(false),
1328 mHasPrintCallbacks(false),
1329 mInUnlinkOrDeletion(false),
1330 mHasHadScriptHandlingObject(false),
1331 mIsBeingUsedAsImage(false),
1332 mDocURISchemeIsChrome(false),
1333 mInChromeDocShell(false),
1334 mIsDevToolsDocument(false),
1335 mIsSyntheticDocument(false),
1336 mHasLinksToUpdateRunnable(false),
1337 mFlushingPendingLinkUpdates(false),
1338 mMayHaveDOMMutationObservers(false),
1339 mMayHaveAnimationObservers(false),
1340 mHasCSP(false),
1341 mHasUnsafeEvalCSP(false),
1342 mHasUnsafeInlineCSP(false),
1343 mHasCSPDeliveredThroughHeader(false),
1344 mBFCacheDisallowed(false),
1345 mHasHadDefaultView(false),
1346 mStyleSheetChangeEventsEnabled(false),
1347 mShadowRootAttachedEventEnabled(false),
1348 mIsSrcdocDocument(false),
1349 mHasDisplayDocument(false),
1350 mFontFaceSetDirty(true),
1351 mDidFireDOMContentLoaded(true),
1352 mHasScrollLinkedEffect(false),
1353 mFrameRequestCallbacksScheduled(false),
1354 mIsTopLevelContentDocument(false),
1355 mIsContentDocument(false),
1356 mDidCallBeginLoad(false),
1357 mEncodingMenuDisabled(false),
1358 mLinksEnabled(true),
1359 mIsSVGGlyphsDocument(false),
1360 mInDestructor(false),
1361 mIsGoingAway(false),
1362 mInXBLUpdate(false),
1363 mNeedsReleaseAfterStackRefCntRelease(false),
1364 mStyleSetFilled(false),
1365 mQuirkSheetAdded(false),
1366 mContentEditableSheetAdded(false),
1367 mDesignModeSheetAdded(false),
1368 mSSApplicableStateNotificationPending(false),
1369 mMayHaveTitleElement(false),
1370 mDOMLoadingSet(false),
1371 mDOMInteractiveSet(false),
1372 mDOMCompleteSet(false),
1373 mAutoFocusFired(false),
1374 mScrolledToRefAlready(false),
1375 mChangeScrollPosWhenScrollingToRef(false),
1376 mDelayFrameLoaderInitialization(false),
1377 mSynchronousDOMContentLoaded(false),
1378 mMaybeServiceWorkerControlled(false),
1379 mAllowZoom(false),
1380 mValidScaleFloat(false),
1381 mValidMinScale(false),
1382 mValidMaxScale(false),
1383 mWidthStrEmpty(false),
1384 mParserAborted(false),
1385 mReportedDocumentUseCounters(false),
1386 mHasReportedShadowDOMUsage(false),
1387 mHasDelayedRefreshEvent(false),
1388 mLoadEventFiring(false),
1389 mSkipLoadEventAfterClose(false),
1390 mDisableCookieAccess(false),
1391 mDisableDocWrite(false),
1392 mTooDeepWriteRecursion(false),
1393 mPendingMaybeEditingStateChanged(false),
1394 mHasBeenEditable(false),
1395 mHasWarnedAboutZoom(false),
1396 mIsRunningExecCommand(false),
1397 mSetCompleteAfterDOMContentLoaded(false),
1398 mDidHitCompleteSheetCache(false),
1399 mPendingFullscreenRequests(0),
1400 mXMLDeclarationBits(0),
1401 mOnloadBlockCount(0),
1402 mWriteLevel(0),
1403 mLazyLoadImageCount(0),
1404 mLazyLoadImageStarted(0),
1405 mLazyLoadImageReachViewportLoading(0),
1406 mLazyLoadImageReachViewportLoaded(0),
1407 mContentEditableCount(0),
1408 mEditingState(EditingState::eOff),
1409 mCompatMode(eCompatibility_FullStandards),
1410 mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
1411 mAncestorIsLoading(false),
1412 mVisibilityState(dom::VisibilityState::Hidden),
1413 mType(eUnknown),
1414 mDefaultElementType(0),
1415 mAllowXULXBL(eTriUnset),
1416 mSkipDTDSecurityChecks(false),
1417 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
1418 mSandboxFlags(0),
1419 mPartID(0),
1420 mMarkedCCGeneration(0),
1421 mPresShell(nullptr),
1422 mSubtreeModifiedDepth(0),
1423 mPreloadPictureDepth(0),
1424 mEventsSuppressed(0),
1425 mIgnoreDestructiveWritesCounter(0),
1426 mStaticCloneCount(0),
1427 mWindow(nullptr),
1428 mBFCacheEntry(nullptr),
1429 mInSyncOperationCount(0),
1430 mBlockDOMContentLoaded(0),
1431 mUseCountersInitialized(false),
1432 mShouldReportUseCounters(false),
1433 mShouldSendPageUseCounters(false),
1434 mUserHasInteracted(false),
1435 mHasUserInteractionTimerScheduled(false),
1436 mStackRefCnt(0),
1437 mUpdateNestLevel(0),
1438 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
1439 mViewportType(Unknown),
1440 mViewportFit(ViewportFitType::Auto),
1441 mSubDocuments(nullptr),
1442 mHeaderData(nullptr),
1443 mFlashClassification(FlashClassification::Unknown),
1444 mServoRestyleRootDirtyBits(0),
1445 mThrowOnDynamicMarkupInsertionCounter(0),
1446 mIgnoreOpensDuringUnloadCounter(0),
1447 mSavedResolution(1.0f),
1448 mSavedResolutionBeforeMVM(1.0f),
1449 mGeneration(0),
1450 mCachedTabSizeGeneration(0),
1451 mNextFormNumber(0),
1452 mNextControlNumber(0),
1453 mPreloadService(this),
1454 mShouldNotifyFetchSuccess(false),
1455 mShouldNotifyFormOrPasswordRemoved(false) {
1456 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
1457
1458 SetIsInDocument();
1459 SetIsConnected(true);
1460
1461 // Create these unconditionally, they will be used to warn about the `zoom`
1462 // property, even if use counters are disabled.
1463 mStyleUseCounters = Servo_UseCounters_Create().Consume();
1464
1465 SetContentType(nsDependentCString(aContentType));
1466
1467 // Start out mLastStyleSheetSet as null, per spec
1468 SetDOMStringToNull(mLastStyleSheetSet);
1469
1470 // void state used to differentiate an empty source from an unselected source
1471 mPreloadPictureFoundSource.SetIsVoid(true);
1472
1473 RecomputeLanguageFromCharset();
1474
1475 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr);
1476 mReferrerInfo = new dom::ReferrerInfo(nullptr);
1477 }
1478
1479 #ifndef ANDROID
1480 // unused by GeckoView
IsAboutErrorPage(nsGlobalWindowInner * aWin,const char * aSpec)1481 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
1482 if (NS_WARN_IF(!aWin)) {
1483 return false;
1484 }
1485
1486 nsIURI* uri = aWin->GetDocumentURI();
1487 if (NS_WARN_IF(!uri)) {
1488 return false;
1489 }
1490 // getSpec is an expensive operation, hence we first check the scheme
1491 // to see if the caller is actually an about: page.
1492 if (!uri->SchemeIs("about")) {
1493 return false;
1494 }
1495
1496 nsAutoCString aboutSpec;
1497 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec);
1498 NS_ENSURE_SUCCESS(rv, false);
1499
1500 return aboutSpec.EqualsASCII(aSpec);
1501 }
1502 #endif
1503
CallerIsTrustedAboutNetError(JSContext * aCx,JSObject * aObject)1504 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
1505 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1506 #ifdef ANDROID
1507 // GeckoView uses data URLs for error pages, so for now just check for any
1508 // error page
1509 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1510 #else
1511 return win && IsAboutErrorPage(win, "neterror");
1512 #endif
1513 }
1514
CallerIsTrustedAboutHttpsOnlyError(JSContext * aCx,JSObject * aObject)1515 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx,
1516 JSObject* aObject) {
1517 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1518 #ifdef ANDROID
1519 // GeckoView uses data URLs for error pages, so for now just check for any
1520 // error page
1521 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1522 #else
1523 return win && IsAboutErrorPage(win, "httpsonlyerror");
1524 #endif
1525 }
1526
AddCertException(bool aIsTemporary)1527 already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
1528 bool aIsTemporary) {
1529 nsIGlobalObject* global = GetScopeObject();
1530 if (!global) {
1531 return nullptr;
1532 }
1533
1534 ErrorResult er;
1535 RefPtr<Promise> promise =
1536 Promise::Create(global, er, Promise::ePropagateUserInteraction);
1537 if (er.Failed()) {
1538 return nullptr;
1539 }
1540
1541 nsCOMPtr<nsISupports> info;
1542 nsCOMPtr<nsITransportSecurityInfo> tsi;
1543 nsresult rv = NS_OK;
1544 if (NS_WARN_IF(!mFailedChannel)) {
1545 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1546 return promise.forget();
1547 }
1548
1549 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1550 if (NS_WARN_IF(NS_FAILED(rv))) {
1551 promise->MaybeReject(rv);
1552 return promise.forget();
1553 }
1554 nsCOMPtr<nsIURI> failedChannelURI;
1555 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
1556 if (!failedChannelURI) {
1557 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1558 return promise.forget();
1559 }
1560
1561 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI);
1562 if (!innerURI) {
1563 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1564 return promise.forget();
1565 }
1566
1567 nsAutoCString host;
1568 innerURI->GetAsciiHost(host);
1569 int32_t port;
1570 innerURI->GetPort(&port);
1571
1572 tsi = do_QueryInterface(info);
1573 if (NS_WARN_IF(!tsi)) {
1574 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1575 return promise.forget();
1576 }
1577
1578 bool isUntrusted = true;
1579 rv = tsi->GetIsUntrusted(&isUntrusted);
1580 if (NS_WARN_IF(NS_FAILED(rv))) {
1581 promise->MaybeReject(rv);
1582 return promise.forget();
1583 }
1584
1585 bool isDomainMismatch = true;
1586 rv = tsi->GetIsDomainMismatch(&isDomainMismatch);
1587 if (NS_WARN_IF(NS_FAILED(rv))) {
1588 promise->MaybeReject(rv);
1589 return promise.forget();
1590 }
1591
1592 bool isNotValidAtThisTime = true;
1593 rv = tsi->GetIsNotValidAtThisTime(&isNotValidAtThisTime);
1594 if (NS_WARN_IF(NS_FAILED(rv))) {
1595 promise->MaybeReject(rv);
1596 return promise.forget();
1597 }
1598
1599 nsCOMPtr<nsIX509Cert> cert;
1600 rv = tsi->GetServerCert(getter_AddRefs(cert));
1601 if (NS_WARN_IF(NS_FAILED(rv))) {
1602 promise->MaybeReject(rv);
1603 return promise.forget();
1604 }
1605 if (NS_WARN_IF(!cert)) {
1606 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1607 return promise.forget();
1608 }
1609
1610 uint32_t flags = 0;
1611 if (isUntrusted) {
1612 flags |= nsICertOverrideService::ERROR_UNTRUSTED;
1613 }
1614 if (isDomainMismatch) {
1615 flags |= nsICertOverrideService::ERROR_MISMATCH;
1616 }
1617 if (isNotValidAtThisTime) {
1618 flags |= nsICertOverrideService::ERROR_TIME;
1619 }
1620
1621 if (XRE_IsContentProcess()) {
1622 nsCOMPtr<nsISerializable> certSer = do_QueryInterface(cert);
1623 nsCString certSerialized;
1624 NS_SerializeToString(certSer, certSerialized);
1625
1626 ContentChild* cc = ContentChild::GetSingleton();
1627 MOZ_ASSERT(cc);
1628 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1629 cc->SendAddCertException(certSerialized, flags, host, port, attrs,
1630 aIsTemporary)
1631 ->Then(GetCurrentSerialEventTarget(), __func__,
1632 [promise](const mozilla::MozPromise<
1633 nsresult, mozilla::ipc::ResponseRejectReason,
1634 true>::ResolveOrRejectValue& aValue) {
1635 if (aValue.IsResolve()) {
1636 promise->MaybeResolve(aValue.ResolveValue());
1637 } else {
1638 promise->MaybeRejectWithUndefined();
1639 }
1640 });
1641 return promise.forget();
1642 }
1643
1644 if (XRE_IsParentProcess()) {
1645 nsCOMPtr<nsICertOverrideService> overrideService =
1646 do_GetService(NS_CERTOVERRIDE_CONTRACTID);
1647 if (!overrideService) {
1648 promise->MaybeReject(NS_ERROR_FAILURE);
1649 return promise.forget();
1650 }
1651
1652 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef();
1653 rv = overrideService->RememberValidityOverride(host, port, attrs, cert,
1654 flags, aIsTemporary);
1655 if (NS_WARN_IF(NS_FAILED(rv))) {
1656 promise->MaybeReject(rv);
1657 return promise.forget();
1658 }
1659
1660 promise->MaybeResolveWithUndefined();
1661 return promise.forget();
1662 }
1663
1664 promise->MaybeReject(NS_ERROR_FAILURE);
1665 return promise.forget();
1666 }
1667
ReloadWithHttpsOnlyException()1668 void Document::ReloadWithHttpsOnlyException() {
1669 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) {
1670 wgc->SendReloadWithHttpsOnlyException();
1671 }
1672 }
1673
GetNetErrorInfo(NetErrorInfo & aInfo,ErrorResult & aRv)1674 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
1675 nsCOMPtr<nsISupports> info;
1676 nsCOMPtr<nsITransportSecurityInfo> tsi;
1677 nsresult rv = NS_OK;
1678 if (NS_WARN_IF(!mFailedChannel)) {
1679 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1680 return;
1681 }
1682
1683 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1684 if (NS_WARN_IF(NS_FAILED(rv))) {
1685 aRv.Throw(rv);
1686 return;
1687 }
1688 tsi = do_QueryInterface(info);
1689 if (NS_WARN_IF(!tsi)) {
1690 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1691 return;
1692 }
1693
1694 nsAutoString errorCodeString;
1695 rv = tsi->GetErrorCodeString(errorCodeString);
1696 if (NS_WARN_IF(NS_FAILED(rv))) {
1697 aRv.Throw(rv);
1698 return;
1699 }
1700 aInfo.mErrorCodeString.Assign(errorCodeString);
1701 }
1702
CallerIsTrustedAboutCertError(JSContext * aCx,JSObject * aObject)1703 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
1704 JSObject* aObject) {
1705 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
1706 #ifdef ANDROID
1707 // GeckoView uses data URLs for error pages, so for now just check for any
1708 // error page
1709 return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
1710 #else
1711 return win && IsAboutErrorPage(win, "certerror");
1712 #endif
1713 }
1714
CallerCanAccessPrivilegeSSA(JSContext * aCx,JSObject * aObject)1715 bool Document::CallerCanAccessPrivilegeSSA(JSContext* aCx, JSObject* aObject) {
1716 RefPtr<BasePrincipal> principal =
1717 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx));
1718
1719 if (!principal) {
1720 return false;
1721 }
1722
1723 // We allow the privilege SSA to be called from system principal.
1724 if (principal->IsSystemPrincipal()) {
1725 return true;
1726 }
1727
1728 // We only allow calling the privilege SSA from the content script of the
1729 // webcompat extension.
1730 if (auto* policy = principal->ContentScriptAddonPolicy()) {
1731 nsAutoString addonID;
1732 policy->GetId(addonID);
1733
1734 return addonID.EqualsLiteral("webcompat@mozilla.org");
1735 }
1736
1737 return false;
1738 }
1739
IsErrorPage() const1740 bool Document::IsErrorPage() const {
1741 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
1742 return loadInfo && loadInfo->GetLoadErrorPage();
1743 }
1744
GetFailedCertSecurityInfo(FailedCertSecurityInfo & aInfo,ErrorResult & aRv)1745 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
1746 ErrorResult& aRv) {
1747 nsCOMPtr<nsISupports> info;
1748 nsCOMPtr<nsITransportSecurityInfo> tsi;
1749 nsresult rv = NS_OK;
1750 if (NS_WARN_IF(!mFailedChannel)) {
1751 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1752 return;
1753 }
1754
1755 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
1756 if (NS_WARN_IF(NS_FAILED(rv))) {
1757 aRv.Throw(rv);
1758 return;
1759 }
1760 tsi = do_QueryInterface(info);
1761 if (NS_WARN_IF(!tsi)) {
1762 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1763 return;
1764 }
1765
1766 nsAutoString errorCodeString;
1767 rv = tsi->GetErrorCodeString(errorCodeString);
1768 if (NS_WARN_IF(NS_FAILED(rv))) {
1769 aRv.Throw(rv);
1770 return;
1771 }
1772 aInfo.mErrorCodeString.Assign(errorCodeString);
1773
1774 rv = tsi->GetIsUntrusted(&aInfo.mIsUntrusted);
1775 if (NS_WARN_IF(NS_FAILED(rv))) {
1776 aRv.Throw(rv);
1777 return;
1778 }
1779
1780 rv = tsi->GetIsDomainMismatch(&aInfo.mIsDomainMismatch);
1781 if (NS_WARN_IF(NS_FAILED(rv))) {
1782 aRv.Throw(rv);
1783 return;
1784 }
1785
1786 rv = tsi->GetIsNotValidAtThisTime(&aInfo.mIsNotValidAtThisTime);
1787 if (NS_WARN_IF(NS_FAILED(rv))) {
1788 aRv.Throw(rv);
1789 return;
1790 }
1791
1792 nsCOMPtr<nsIX509Cert> cert;
1793 nsCOMPtr<nsIX509CertValidity> validity;
1794 rv = tsi->GetServerCert(getter_AddRefs(cert));
1795 if (NS_WARN_IF(NS_FAILED(rv))) {
1796 aRv.Throw(rv);
1797 return;
1798 }
1799 if (NS_WARN_IF(!cert)) {
1800 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1801 return;
1802 }
1803
1804 rv = cert->GetValidity(getter_AddRefs(validity));
1805 if (NS_WARN_IF(NS_FAILED(rv))) {
1806 aRv.Throw(rv);
1807 return;
1808 }
1809 if (NS_WARN_IF(!validity)) {
1810 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1811 return;
1812 }
1813
1814 PRTime validityResult;
1815 rv = validity->GetNotBefore(&validityResult);
1816 if (NS_WARN_IF(NS_FAILED(rv))) {
1817 aRv.Throw(rv);
1818 return;
1819 }
1820 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1821
1822 rv = validity->GetNotAfter(&validityResult);
1823 if (NS_WARN_IF(NS_FAILED(rv))) {
1824 aRv.Throw(rv);
1825 return;
1826 }
1827 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC);
1828
1829 nsAutoString issuerCommonName;
1830 nsAutoString certChainPEMString;
1831 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct();
1832 int64_t maxValidity = std::numeric_limits<int64_t>::max();
1833 int64_t minValidity = 0;
1834 PRTime notBefore, notAfter;
1835 nsTArray<RefPtr<nsIX509Cert>> failedCertArray;
1836 rv = tsi->GetFailedCertChain(failedCertArray);
1837 if (NS_WARN_IF(NS_FAILED(rv))) {
1838 aRv.Throw(rv);
1839 return;
1840 }
1841
1842 if (NS_WARN_IF(failedCertArray.IsEmpty())) {
1843 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1844 return;
1845 }
1846
1847 for (const auto& certificate : failedCertArray) {
1848 rv = certificate->GetIssuerCommonName(issuerCommonName);
1849 if (NS_WARN_IF(NS_FAILED(rv))) {
1850 aRv.Throw(rv);
1851 return;
1852 }
1853
1854 rv = certificate->GetValidity(getter_AddRefs(validity));
1855 if (NS_WARN_IF(NS_FAILED(rv))) {
1856 aRv.Throw(rv);
1857 return;
1858 }
1859 if (NS_WARN_IF(!validity)) {
1860 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1861 return;
1862 }
1863
1864 rv = validity->GetNotBefore(¬Before);
1865 if (NS_WARN_IF(NS_FAILED(rv))) {
1866 aRv.Throw(rv);
1867 return;
1868 }
1869
1870 rv = validity->GetNotAfter(¬After);
1871 if (NS_WARN_IF(NS_FAILED(rv))) {
1872 aRv.Throw(rv);
1873 return;
1874 }
1875
1876 notBefore = std::max(minValidity, notBefore);
1877 notAfter = std::min(maxValidity, notAfter);
1878 nsTArray<uint8_t> certArray;
1879 rv = certificate->GetRawDER(certArray);
1880 if (NS_WARN_IF(NS_FAILED(rv))) {
1881 aRv.Throw(rv);
1882 return;
1883 }
1884
1885 nsAutoString der64;
1886 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()),
1887 certArray.Length(), der64);
1888 if (NS_WARN_IF(NS_FAILED(rv))) {
1889 aRv.Throw(rv);
1890 return;
1891 }
1892 if (!certChainStrings.AppendElement(der64, fallible)) {
1893 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1894 return;
1895 }
1896 }
1897
1898 aInfo.mIssuerCommonName.Assign(issuerCommonName);
1899 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC);
1900 aInfo.mCertValidityRangeNotBefore =
1901 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC);
1902
1903 int32_t errorCode;
1904 rv = tsi->GetErrorCode(&errorCode);
1905 if (NS_WARN_IF(NS_FAILED(rv))) {
1906 aRv.Throw(rv);
1907 return;
1908 }
1909
1910 nsCOMPtr<nsINSSErrorsService> nsserr =
1911 do_GetService("@mozilla.org/nss_errors_service;1");
1912 if (NS_WARN_IF(!nsserr)) {
1913 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1914 return;
1915 }
1916 nsresult res;
1917 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res);
1918 if (NS_WARN_IF(NS_FAILED(rv))) {
1919 aRv.Throw(rv);
1920 return;
1921 }
1922 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage);
1923 if (NS_WARN_IF(NS_FAILED(rv))) {
1924 aRv.Throw(rv);
1925 return;
1926 }
1927
1928 bool isPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(this);
1929 uint32_t flags =
1930 isPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
1931 OriginAttributes attrs;
1932 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs);
1933 nsCOMPtr<nsIURI> aURI;
1934 mFailedChannel->GetURI(getter_AddRefs(aURI));
1935 if (XRE_IsContentProcess()) {
1936 ContentChild* cc = ContentChild::GetSingleton();
1937 MOZ_ASSERT(cc);
1938 cc->SendIsSecureURI(aURI, flags, attrs, &aInfo.mHasHSTS);
1939 } else {
1940 nsCOMPtr<nsISiteSecurityService> sss =
1941 do_GetService(NS_SSSERVICE_CONTRACTID);
1942 if (NS_WARN_IF(!sss)) {
1943 return;
1944 }
1945 Unused << NS_WARN_IF(NS_FAILED(sss->IsSecureURI(aURI, flags, attrs, nullptr,
1946 nullptr, &aInfo.mHasHSTS)));
1947 }
1948 nsCOMPtr<nsIPublicKeyPinningService> pkps =
1949 do_GetService(NS_PKPSERVICE_CONTRACTID);
1950 if (NS_WARN_IF(!pkps)) {
1951 return;
1952 }
1953 Unused << NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP)));
1954 }
1955
AllowDeprecatedTls()1956 bool Document::AllowDeprecatedTls() {
1957 return Preferences::GetBool("security.tls.version.enable-deprecated", false);
1958 }
1959
SetAllowDeprecatedTls(bool value)1960 void Document::SetAllowDeprecatedTls(bool value) {
1961 if (!IsErrorPage()) {
1962 return;
1963 }
1964
1965 auto docShell = GetDocShell();
1966 if (!docShell) {
1967 return;
1968 }
1969
1970 auto child = BrowserChild::GetFrom(docShell);
1971 if (!child) {
1972 return;
1973 }
1974
1975 child->SendSetAllowDeprecatedTls(value);
1976 }
1977
IsAboutPage() const1978 bool Document::IsAboutPage() const {
1979 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
1980 return principal->SchemeIs("about");
1981 }
1982
ConstructUbiNode(void * storage)1983 void Document::ConstructUbiNode(void* storage) {
1984 JS::ubi::Concrete<Document>::construct(storage, this);
1985 }
1986
LoadEventFired()1987 void Document::LoadEventFired() {
1988 // Accumulate timing data located in each document's realm and report to
1989 // telemetry.
1990 AccumulateJSTelemetry();
1991
1992 // Collect page load timings
1993 AccumulatePageLoadTelemetry();
1994
1995 // Release the JS bytecode cache from its wait on the load event, and
1996 // potentially dispatch the encoding of the bytecode.
1997 if (ScriptLoader()) {
1998 ScriptLoader()->LoadEventFired();
1999 }
2000 }
2001
AccumulatePageLoadTelemetry()2002 void Document::AccumulatePageLoadTelemetry() {
2003 // Interested only in top level documents for real websites that are in the
2004 // foreground.
2005 if (!ShouldIncludeInTelemetry(false) || !IsTopLevelContentDocument() ||
2006 !GetNavigationTiming() ||
2007 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) {
2008 return;
2009 }
2010
2011 if (!GetChannel()) {
2012 return;
2013 }
2014
2015 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel()));
2016 if (!timedChannel) {
2017 return;
2018 }
2019
2020 TimeStamp responseStart;
2021 timedChannel->GetResponseStart(&responseStart);
2022
2023 TimeStamp navigationStart =
2024 GetNavigationTiming()->GetNavigationStartTimeStamp();
2025
2026 if (!responseStart || !navigationStart) {
2027 return;
2028 }
2029
2030 nsCString http3Key;
2031 nsCString http3WithPriorityKey;
2032 nsCOMPtr<nsIHttpChannelInternal> httpChannel =
2033 do_QueryInterface(GetChannel());
2034 if (httpChannel) {
2035 uint32_t major;
2036 uint32_t minor;
2037 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) {
2038 if (major == 3) {
2039 http3Key = "http3"_ns;
2040 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel());
2041 nsCString header;
2042 if (httpChannel2 &&
2043 NS_SUCCEEDED(
2044 httpChannel2->GetResponseHeader("priority"_ns, header)) &&
2045 !header.IsEmpty()) {
2046 http3WithPriorityKey = "with_priority"_ns;
2047 } else {
2048 http3WithPriorityKey = "without_priority"_ns;
2049 }
2050 } else if (major == 2) {
2051 bool supportHttp3 = false;
2052 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) {
2053 supportHttp3 = false;
2054 }
2055 if (supportHttp3) {
2056 http3Key = "supports_http3"_ns;
2057 }
2058 }
2059 }
2060 }
2061
2062 // First Contentful Composite
2063 if (TimeStamp firstContentfulComposite =
2064 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) {
2065 Telemetry::AccumulateTimeDelta(Telemetry::PERF_FIRST_CONTENTFUL_PAINT_MS,
2066 navigationStart, firstContentfulComposite);
2067
2068 if (!http3Key.IsEmpty()) {
2069 Telemetry::AccumulateTimeDelta(
2070 Telemetry::HTTP3_PERF_FIRST_CONTENTFUL_PAINT_MS, http3Key,
2071 navigationStart, firstContentfulComposite);
2072 }
2073
2074 if (!http3WithPriorityKey.IsEmpty()) {
2075 Telemetry::AccumulateTimeDelta(
2076 Telemetry::H3P_PERF_FIRST_CONTENTFUL_PAINT_MS, http3WithPriorityKey,
2077 navigationStart, firstContentfulComposite);
2078 }
2079
2080 Telemetry::AccumulateTimeDelta(
2081 Telemetry::PERF_FIRST_CONTENTFUL_PAINT_FROM_RESPONSESTART_MS,
2082 responseStart, firstContentfulComposite);
2083 }
2084
2085 // DOM Content Loaded event
2086 if (TimeStamp dclEventStart =
2087 GetNavigationTiming()->GetDOMContentLoadedEventStartTimeStamp()) {
2088 Telemetry::AccumulateTimeDelta(Telemetry::PERF_DOM_CONTENT_LOADED_TIME_MS,
2089 navigationStart, dclEventStart);
2090 Telemetry::AccumulateTimeDelta(
2091 Telemetry::PERF_DOM_CONTENT_LOADED_TIME_FROM_RESPONSESTART_MS,
2092 responseStart, dclEventStart);
2093 }
2094
2095 // Load event
2096 if (TimeStamp loadEventStart =
2097 GetNavigationTiming()->GetLoadEventStartTimeStamp()) {
2098 Telemetry::AccumulateTimeDelta(Telemetry::PERF_PAGE_LOAD_TIME_MS,
2099 navigationStart, loadEventStart);
2100 if (!http3Key.IsEmpty()) {
2101 Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_PERF_PAGE_LOAD_TIME_MS,
2102 http3Key, navigationStart, loadEventStart);
2103 }
2104
2105 if (!http3WithPriorityKey.IsEmpty()) {
2106 Telemetry::AccumulateTimeDelta(Telemetry::H3P_PERF_PAGE_LOAD_TIME_MS,
2107 http3WithPriorityKey, navigationStart,
2108 loadEventStart);
2109 }
2110
2111 Telemetry::AccumulateTimeDelta(
2112 Telemetry::PERF_PAGE_LOAD_TIME_FROM_RESPONSESTART_MS, responseStart,
2113 loadEventStart);
2114 }
2115 }
2116
AccumulateJSTelemetry()2117 void Document::AccumulateJSTelemetry() {
2118 if (!IsTopLevelContentDocument() || !ShouldIncludeInTelemetry(false)) {
2119 return;
2120 }
2121
2122 if (!GetScopeObject() || !GetScopeObject()->GetGlobalJSObject()) {
2123 return;
2124 }
2125
2126 AutoJSContext cx;
2127 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
2128 JSAutoRealm ar(cx, globalObject);
2129 JS::JSTimers timers = JS::GetJSTimers(cx);
2130
2131 if (!timers.executionTime.IsZero()) {
2132 Telemetry::Accumulate(
2133 Telemetry::JS_PAGELOAD_EXECUTION_MS,
2134 static_cast<uint32_t>(timers.executionTime.ToMilliseconds()));
2135 }
2136
2137 if (!timers.delazificationTime.IsZero()) {
2138 Telemetry::Accumulate(
2139 Telemetry::JS_PAGELOAD_DELAZIFICATION_MS,
2140 static_cast<uint32_t>(timers.delazificationTime.ToMilliseconds()));
2141 }
2142
2143 if (!timers.xdrEncodingTime.IsZero()) {
2144 Telemetry::Accumulate(
2145 Telemetry::JS_PAGELOAD_XDR_ENCODING_MS,
2146 static_cast<uint32_t>(timers.xdrEncodingTime.ToMilliseconds()));
2147 }
2148
2149 if (!timers.baselineCompileTime.IsZero()) {
2150 Telemetry::Accumulate(
2151 Telemetry::JS_PAGELOAD_BASELINE_COMPILE_MS,
2152 static_cast<uint32_t>(timers.baselineCompileTime.ToMilliseconds()));
2153 }
2154
2155 if (!timers.gcTime.IsZero()) {
2156 Telemetry::Accumulate(
2157 Telemetry::JS_PAGELOAD_GC_MS,
2158 static_cast<uint32_t>(timers.gcTime.ToMilliseconds()));
2159 }
2160
2161 if (!timers.protectTime.IsZero()) {
2162 Telemetry::Accumulate(
2163 Telemetry::JS_PAGELOAD_PROTECT_MS,
2164 static_cast<uint32_t>(timers.protectTime.ToMilliseconds()));
2165 }
2166 }
2167
~Document()2168 Document::~Document() {
2169 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this));
2170 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(),
2171 "Can't be top-level and a resource doc at the same time");
2172
2173 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
2174
2175 if (IsTopLevelContentDocument()) {
2176 RemoveToplevelLoadingDocument(this);
2177
2178 // don't report for about: pages
2179 if (!IsAboutPage()) {
2180 // record CSP telemetry on this document
2181 if (mHasCSP) {
2182 Accumulate(Telemetry::CSP_DOCUMENTS_COUNT, 1);
2183 }
2184 if (mHasUnsafeInlineCSP) {
2185 Accumulate(Telemetry::CSP_UNSAFE_INLINE_DOCUMENTS_COUNT, 1);
2186 }
2187 if (mHasUnsafeEvalCSP) {
2188 Accumulate(Telemetry::CSP_UNSAFE_EVAL_DOCUMENTS_COUNT, 1);
2189 }
2190
2191 if (MOZ_UNLIKELY(mMathMLEnabled)) {
2192 ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
2193 }
2194
2195 if (IsHTMLDocument()) {
2196 switch (GetCompatibilityMode()) {
2197 case eCompatibility_FullStandards:
2198 Telemetry::AccumulateCategorical(
2199 Telemetry::LABELS_QUIRKS_MODE::FullStandards);
2200 break;
2201 case eCompatibility_AlmostStandards:
2202 Telemetry::AccumulateCategorical(
2203 Telemetry::LABELS_QUIRKS_MODE::AlmostStandards);
2204 break;
2205 case eCompatibility_NavQuirks:
2206 Telemetry::AccumulateCategorical(
2207 Telemetry::LABELS_QUIRKS_MODE::NavQuirks);
2208 break;
2209 default:
2210 MOZ_ASSERT_UNREACHABLE("Unknown quirks mode");
2211 break;
2212 }
2213 }
2214 }
2215 }
2216
2217 mInDestructor = true;
2218 mInUnlinkOrDeletion = true;
2219
2220 mozilla::DropJSObjects(this);
2221
2222 // Clear mObservers to keep it in sync with the mutationobserver list
2223 mObservers.Clear();
2224
2225 mIntersectionObservers.Clear();
2226
2227 if (mStyleSheetSetList) {
2228 mStyleSheetSetList->Disconnect();
2229 }
2230
2231 if (mAnimationController) {
2232 mAnimationController->Disconnect();
2233 }
2234
2235 MOZ_ASSERT(mTimelines.isEmpty());
2236
2237 mParentDocument = nullptr;
2238
2239 // Kill the subdocument map, doing this will release its strong
2240 // references, if any.
2241 delete mSubDocuments;
2242 mSubDocuments = nullptr;
2243
2244 nsAutoScriptBlocker scriptBlocker;
2245
2246 // Destroy link map now so we don't waste time removing
2247 // links one by one
2248 DestroyElementMaps();
2249
2250 // Invalidate cached array of child nodes
2251 InvalidateChildNodes();
2252
2253 // We should not have child nodes when destructor is called,
2254 // since child nodes keep their owner document alive.
2255 MOZ_ASSERT(!HasChildren());
2256
2257 mCachedRootElement = nullptr;
2258
2259 for (auto& sheets : mAdditionalSheets) {
2260 UnlinkStyleSheets(sheets);
2261 }
2262
2263 if (mAttrStyleSheet) {
2264 mAttrStyleSheet->SetOwningDocument(nullptr);
2265 }
2266
2267 if (mListenerManager) {
2268 mListenerManager->Disconnect();
2269 UnsetFlags(NODE_HAS_LISTENERMANAGER);
2270 }
2271
2272 if (mScriptLoader) {
2273 mScriptLoader->DropDocumentReference();
2274 }
2275
2276 if (mCSSLoader) {
2277 // Could be null here if Init() failed or if we have been unlinked.
2278 mCSSLoader->DropDocumentReference();
2279 }
2280
2281 if (mStyleImageLoader) {
2282 mStyleImageLoader->DropDocumentReference();
2283 }
2284
2285 if (mXULBroadcastManager) {
2286 mXULBroadcastManager->DropDocumentReference();
2287 }
2288
2289 if (mXULPersist) {
2290 mXULPersist->DropDocumentReference();
2291 }
2292
2293 if (mPermissionDelegateHandler) {
2294 mPermissionDelegateHandler->DropDocumentReference();
2295 }
2296
2297 mHeaderData = nullptr;
2298
2299 mPendingTitleChangeEvent.Revoke();
2300
2301 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
2302 "must not have media query lists left");
2303
2304 if (mNodeInfoManager) {
2305 mNodeInfoManager->DropDocumentReference();
2306 }
2307
2308 if (mDocGroup) {
2309 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup());
2310 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup);
2311 }
2312
2313 UnlinkOriginalDocumentIfStatic();
2314
2315 UnregisterFromMemoryReportingForDataDocument();
2316 }
2317
NS_INTERFACE_TABLE_HEAD(Document)2318 NS_INTERFACE_TABLE_HEAD(Document)
2319 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
2320 NS_INTERFACE_TABLE_BEGIN
2321 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode)
2322 NS_INTERFACE_TABLE_ENTRY(Document, nsINode)
2323 NS_INTERFACE_TABLE_ENTRY(Document, Document)
2324 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal)
2325 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget)
2326 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference)
2327 NS_INTERFACE_TABLE_ENTRY(Document, nsIRadioGroupContainer)
2328 NS_INTERFACE_TABLE_END
2329 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document)
2330 NS_INTERFACE_MAP_END
2331
2332 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document)
2333 NS_IMETHODIMP_(MozExternalRefCountType)
2334 Document::Release() {
2335 MOZ_ASSERT(0 != mRefCnt, "dup release");
2336 NS_ASSERT_OWNINGTHREAD(Document);
2337 nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(Document)::Upcast(this);
2338 bool shouldDelete = false;
2339 nsrefcnt count = mRefCnt.decr(base, &shouldDelete);
2340 NS_LOG_RELEASE(this, count, "Document");
2341 if (count == 0) {
2342 if (mStackRefCnt && !mNeedsReleaseAfterStackRefCntRelease) {
2343 mNeedsReleaseAfterStackRefCntRelease = true;
2344 NS_ADDREF_THIS();
2345 return mRefCnt.get();
2346 }
2347 mRefCnt.incr(base);
2348 LastRelease();
2349 mRefCnt.decr(base);
2350 if (shouldDelete) {
2351 mRefCnt.stabilizeForDeletion();
2352 DeleteCycleCollectable();
2353 }
2354 }
2355 return count;
2356 }
2357
NS_IMETHODIMP_(void)2358 NS_IMETHODIMP_(void)
2359 Document::DeleteCycleCollectable() { delete this; }
2360
2361 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document)
2362 if (Element::CanSkip(tmp, aRemovingAllowed)) {
2363 EventListenerManager* elm = tmp->GetExistingListenerManager();
2364 if (elm) {
2365 elm->MarkForCC();
2366 }
2367 return true;
2368 }
2369 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
2370
2371 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
2372 return Element::CanSkipInCC(tmp);
2373 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
2374
2375 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
2376 return Element::CanSkipThis(tmp);
2377 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
2378
2379 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
2380 if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
2381 char name[512];
2382 nsAutoCString loadedAsData;
2383 if (tmp->IsLoadedAsData()) {
2384 loadedAsData.AssignLiteral("data");
2385 } else {
2386 loadedAsData.AssignLiteral("normal");
2387 }
2388 uint32_t nsid = tmp->GetDefaultNamespaceID();
2389 nsAutoCString uri;
2390 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
2391 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)",
2392 "(xhtml)", "(XLink)", "(XSLT)",
2393 "(MathML)", "(RDF)", "(XUL)"};
2394 if (nsid < ArrayLength(kNSURIs)) {
2395 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
2396 kNSURIs[nsid], uri.get());
2397 } else {
2398 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
2399 }
2400 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
2401 } else {
2402 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get())
2403 }
2404
2405 if (!nsINode::Traverse(tmp, cb)) {
2406 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
2407 }
2408
2409 tmp->mExternalResourceMap.Traverse(&cb);
2410
2411 // Traverse all Document pointer members.
2412 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
2413 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
2414 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
2415 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
2416 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n)
2417
2418 // Traverse all Document nsCOMPtrs.
2419 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
2420 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
2421 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
2422 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
2423 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
2424
2425 DocumentOrShadowRoot::Traverse(tmp, cb);
2426
2427 for (auto& sheets : tmp->mAdditionalSheets) {
2428 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
2429 }
2430
2431 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
2432 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserver)
2433 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadImageObserverViewport)
2434 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation)
2435 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps)
2436 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise)
2437 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
2438 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
2439 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached)
2440 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
2441 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
2442 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
2443 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
2444 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages);
2445 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds);
2446 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks);
2447 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms);
2448 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts);
2449 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets);
2450 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
2451 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
2452 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
2453 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
2454 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
2455 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
2456 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
2457 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
2458 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2459 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager)
2460
2461 // Traverse all our nsCOMArrays.
2462 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
2463
2464 // Traverse animation components
2465 if (tmp->mAnimationController) {
2466 tmp->mAnimationController->Traverse(&cb);
2467 }
2468
2469 if (tmp->mSubDocuments) {
2470 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
2471 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
2472
2473 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey");
2474 cb.NoteXPCOMChild(entry->mKey);
2475 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
2476 "mSubDocuments entry->mSubDocument");
2477 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument));
2478 }
2479 }
2480
2481 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
2482
2483 // We own only the items in mDOMMediaQueryLists that have listeners;
2484 // this reference is managed by their AddListener and RemoveListener
2485 // methods.
2486 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
2487 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
2488 if (mql->HasListeners() &&
2489 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) {
2490 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
2491 cb.NoteXPCOMChild(mql);
2492 }
2493 }
2494
2495 // XXX: This should be not needed once bug 1569185 lands.
2496 for (const auto& entry : tmp->mL10nProtoElements) {
2497 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key");
2498 cb.NoteXPCOMChild(entry.GetKey());
2499 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value");
2500 }
2501
2502 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) {
2503 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement);
2504 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
2505 mPendingFrameStaticClones[i].mStaticCloneOf);
2506 }
2507 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2508
2509 NS_IMPL_CYCLE_COLLECTION_CLASS(Document)
2510
2511 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Document)
2512
2513 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
2514 tmp->mInUnlinkOrDeletion = true;
2515
2516 // Clear out our external resources
2517 tmp->mExternalResourceMap.Shutdown();
2518
2519 nsAutoScriptBlocker scriptBlocker;
2520
2521 nsINode::Unlink(tmp);
2522
2523 while (tmp->HasChildren()) {
2524 // Hold a strong ref to the node when we remove it, because we may be
2525 // the last reference to it.
2526 // If this code changes, change the corresponding code in Document's
2527 // unlink impl and ContentUnbinder::UnbindSubtree.
2528 nsCOMPtr<nsIContent> child = tmp->GetLastChild();
2529 tmp->DisconnectChild(child);
2530 child->UnbindFromTree();
2531 }
2532
2533 tmp->UnlinkOriginalDocumentIfStatic();
2534
2535 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
2536
2537 tmp->SetScriptGlobalObject(nullptr);
2538
2539 for (auto& sheets : tmp->mAdditionalSheets) {
2540 tmp->UnlinkStyleSheets(sheets);
2541 }
2542
2543 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo)
2544 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
2545 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserver)
2546 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadImageObserverViewport)
2547 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
2548 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle)
2549 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n)
2550 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
2551 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker)
2552 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
2553 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
2554 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
2555 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
2556 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
2557 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStateObjectCached)
2558 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
2559 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
2560 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
2561 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
2562 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages);
2563 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds);
2564 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks);
2565 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms);
2566 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts);
2567 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets);
2568 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors);
2569 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents)
2570 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
2571 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
2572 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
2573 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
2574 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
2575 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
2576 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
2577 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
2578
2579 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
2580 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp,
2581 tmp->mDocGroup);
2582 }
2583 tmp->mDocGroup = nullptr;
2584
2585 if (tmp->IsTopLevelContentDocument()) {
2586 RemoveToplevelLoadingDocument(tmp);
2587 }
2588
2589 tmp->mParentDocument = nullptr;
2590
2591 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
2592
2593 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
2594
2595 if (tmp->mListenerManager) {
2596 tmp->mListenerManager->Disconnect();
2597 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
2598 tmp->mListenerManager = nullptr;
2599 }
2600
2601 if (tmp->mStyleSheetSetList) {
2602 tmp->mStyleSheetSetList->Disconnect();
2603 tmp->mStyleSheetSetList = nullptr;
2604 }
2605
2606 delete tmp->mSubDocuments;
2607 tmp->mSubDocuments = nullptr;
2608
2609 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager)
2610 MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
2611 "How did we get here without our presshell going away "
2612 "first?");
2613
2614 DocumentOrShadowRoot::Unlink(tmp);
2615
2616 // Document has a pretty complex destructor, so we're going to
2617 // assume that *most* cycles you actually want to break somewhere
2618 // else, and not unlink an awful lot here.
2619
2620 tmp->mExpandoAndGeneration.OwnerUnlinked();
2621
2622 if (tmp->mAnimationController) {
2623 tmp->mAnimationController->Unlink();
2624 }
2625
2626 tmp->mPendingTitleChangeEvent.Revoke();
2627
2628 if (tmp->mCSSLoader) {
2629 tmp->mCSSLoader->DropDocumentReference();
2630 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
2631 }
2632
2633 // We own only the items in mDOMMediaQueryLists that have listeners;
2634 // this reference is managed by their AddListener and RemoveListener
2635 // methods.
2636 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
2637 MediaQueryList* next =
2638 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
2639 mql->Disconnect();
2640 mql = next;
2641 }
2642
2643 tmp->mPendingFrameStaticClones.Clear();
2644
2645 tmp->mInUnlinkOrDeletion = false;
2646
2647 tmp->UnregisterFromMemoryReportingForDataDocument();
2648
NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)2649 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements)
2650 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
2651 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
2652 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2653
2654 nsresult Document::Init() {
2655 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
2656 return NS_ERROR_ALREADY_INITIALIZED;
2657 }
2658
2659 // Force initialization.
2660 mOnloadBlocker = new OnloadBlocker();
2661 mStyleImageLoader = new css::ImageLoader(this);
2662
2663 mNodeInfoManager = new nsNodeInfoManager();
2664 nsresult rv = mNodeInfoManager->Init(this);
2665 NS_ENSURE_SUCCESS(rv, rv);
2666
2667 // mNodeInfo keeps NodeInfoManager alive!
2668 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
2669 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
2670 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE,
2671 "Bad NodeType in aNodeInfo");
2672
2673 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
2674
2675 mCSSLoader = new css::Loader(this);
2676 // Assume we're not quirky, until we know otherwise
2677 mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
2678
2679 // If after creation the owner js global is not set for a document
2680 // we use the default compartment for this document, instead of creating
2681 // wrapper in some random compartment when the document is exposed to js
2682 // via some events.
2683 nsCOMPtr<nsIGlobalObject> global =
2684 xpc::NativeGlobal(xpc::PrivilegedJunkScope());
2685 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
2686 mScopeObject = do_GetWeakReference(global);
2687 MOZ_ASSERT(mScopeObject);
2688
2689 mScriptLoader = new dom::ScriptLoader(this);
2690
2691 // we need to create a policy here so getting the policy within
2692 // ::Policy() can *always* return a non null policy
2693 mFeaturePolicy = new dom::FeaturePolicy(this);
2694 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
2695
2696 mStyleSet = MakeUnique<ServoStyleSet>(*this);
2697
2698 mozilla::HoldJSObjects(this);
2699
2700 return NS_OK;
2701 }
2702
RemoveAllProperties()2703 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); }
2704
RemoveAllPropertiesFor(nsINode * aNode)2705 void Document::RemoveAllPropertiesFor(nsINode* aNode) {
2706 PropertyTable().RemoveAllPropertiesFor(aNode);
2707 }
2708
Reset(nsIChannel * aChannel,nsILoadGroup * aLoadGroup)2709 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
2710 nsCOMPtr<nsIURI> uri;
2711 nsCOMPtr<nsIPrincipal> principal;
2712 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
2713 if (aChannel) {
2714 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and
2715 // nsScriptSecurityManager::GetChannelResultPrincipals.
2716 // Note: this should match the uri used for the OnNewURI call in
2717 // nsDocShell::CreateContentViewer.
2718 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
2719
2720 nsIScriptSecurityManager* securityManager =
2721 nsContentUtils::GetSecurityManager();
2722 if (securityManager) {
2723 securityManager->GetChannelResultPrincipals(
2724 aChannel, getter_AddRefs(principal),
2725 getter_AddRefs(partitionedPrincipal));
2726 }
2727 }
2728
2729 bool equal = principal->Equals(partitionedPrincipal);
2730
2731 principal = MaybeDowngradePrincipal(principal);
2732 if (equal) {
2733 partitionedPrincipal = principal;
2734 } else {
2735 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal);
2736 }
2737
2738 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal);
2739
2740 // Note that, since mTiming does not change during a reset, the
2741 // navigationStart time remains unchanged and therefore any future new
2742 // timeline will have the same global clock time as the old one.
2743 mDocumentTimeline = nullptr;
2744
2745 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) {
2746 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) {
2747 mDocumentBaseURI = baseURI.forget();
2748 mChromeXHRDocBaseURI = nullptr;
2749 }
2750 }
2751
2752 mChannel = aChannel;
2753 }
2754
DisconnectNodeTree()2755 void Document::DisconnectNodeTree() {
2756 // Delete references to sub-documents and kill the subdocument map,
2757 // if any. This is not strictly needed, but makes the node tree
2758 // teardown a bit faster.
2759 delete mSubDocuments;
2760 mSubDocuments = nullptr;
2761
2762 bool oldVal = mInUnlinkOrDeletion;
2763 mInUnlinkOrDeletion = true;
2764 { // Scope for update
2765 MOZ_AUTO_DOC_UPDATE(this, true);
2766
2767 // Destroy link map now so we don't waste time removing
2768 // links one by one
2769 DestroyElementMaps();
2770
2771 // Invalidate cached array of child nodes
2772 InvalidateChildNodes();
2773
2774 while (HasChildren()) {
2775 nsMutationGuard::DidMutate();
2776 nsCOMPtr<nsIContent> content = GetLastChild();
2777 nsIContent* previousSibling = content->GetPreviousSibling();
2778 DisconnectChild(content);
2779 if (content == mCachedRootElement) {
2780 // Immediately clear mCachedRootElement, now that it's been removed
2781 // from mChildren, so that GetRootElement() will stop returning this
2782 // now-stale value.
2783 mCachedRootElement = nullptr;
2784 }
2785 MutationObservers::NotifyContentRemoved(this, content, previousSibling);
2786 content->UnbindFromTree();
2787 }
2788 MOZ_ASSERT(!mCachedRootElement,
2789 "After removing all children, there should be no root elem");
2790 }
2791 mInUnlinkOrDeletion = oldVal;
2792 }
2793
ResetToURI(nsIURI * aURI,nsILoadGroup * aLoadGroup,nsIPrincipal * aPrincipal,nsIPrincipal * aPartitionedPrincipal)2794 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
2795 nsIPrincipal* aPrincipal,
2796 nsIPrincipal* aPartitionedPrincipal) {
2797 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
2798 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal);
2799
2800 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
2801 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
2802
2803 mSecurityInfo = nullptr;
2804
2805 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
2806 if (!aLoadGroup || group != aLoadGroup) {
2807 mDocumentLoadGroup = nullptr;
2808 }
2809
2810 DisconnectNodeTree();
2811
2812 // Reset our stylesheets
2813 ResetStylesheetsToURI(aURI);
2814
2815 // Release the listener manager
2816 if (mListenerManager) {
2817 mListenerManager->Disconnect();
2818 mListenerManager = nullptr;
2819 }
2820
2821 // Release the stylesheets list.
2822 mDOMStyleSheets = nullptr;
2823
2824 // Release our principal after tearing down the document, rather than before.
2825 // This ensures that, during teardown, the document and the dying window
2826 // (which already nulled out its document pointer and cached the principal)
2827 // have matching principals.
2828 SetPrincipals(nullptr, nullptr);
2829
2830 // Clear the original URI so SetDocumentURI sets it.
2831 mOriginalURI = nullptr;
2832
2833 SetDocumentURI(aURI);
2834 mChromeXHRDocURI = nullptr;
2835 // If mDocumentBaseURI is null, Document::GetBaseURI() returns
2836 // mDocumentURI.
2837 mDocumentBaseURI = nullptr;
2838 mChromeXHRDocBaseURI = nullptr;
2839
2840 // Check if the current document is the top-level DevTools document.
2841 // For inner DevTools frames, mIsDevToolsDocument will be set when
2842 // calling SetDocumentParent.
2843 if (aURI && aURI->SchemeIs("about") &&
2844 aURI->GetSpecOrDefault().EqualsLiteral("about:devtools-toolbox")) {
2845 mIsDevToolsDocument = true;
2846 }
2847
2848 if (aLoadGroup) {
2849 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
2850 // there was an assertion here that aLoadGroup was not null. This
2851 // is no longer valid: nsDocShell::SetDocument does not create a
2852 // load group, and it works just fine
2853
2854 // XXXbz what does "just fine" mean exactly? And given that there
2855 // is no nsDocShell::SetDocument, what is this talking about?
2856
2857 if (IsContentDocument()) {
2858 // Inform the associated request context about this load start so
2859 // any of its internal load progress flags gets reset.
2860 nsCOMPtr<nsIRequestContextService> rcsvc =
2861 net::RequestContextService::GetOrCreate();
2862 if (rcsvc) {
2863 nsCOMPtr<nsIRequestContext> rc;
2864 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc));
2865 if (rc) {
2866 rc->BeginLoad();
2867 }
2868 }
2869 }
2870 }
2871
2872 mLastModified.Truncate();
2873 // XXXbz I guess we're assuming that the caller will either pass in
2874 // a channel with a useful type or call SetContentType?
2875 SetContentType(""_ns);
2876 mContentLanguage.Truncate();
2877 mBaseTarget.Truncate();
2878
2879 mXMLDeclarationBits = 0;
2880
2881 // Now get our new principal
2882 if (aPrincipal) {
2883 SetPrincipals(aPrincipal, aPartitionedPrincipal);
2884 } else {
2885 nsIScriptSecurityManager* securityManager =
2886 nsContentUtils::GetSecurityManager();
2887 if (securityManager) {
2888 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
2889
2890 if (!loadContext && aLoadGroup) {
2891 nsCOMPtr<nsIInterfaceRequestor> cbs;
2892 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
2893 loadContext = do_GetInterface(cbs);
2894 }
2895
2896 MOZ_ASSERT(loadContext,
2897 "must have a load context or pass in an explicit principal");
2898
2899 nsCOMPtr<nsIPrincipal> principal;
2900 nsresult rv = securityManager->GetLoadContextContentPrincipal(
2901 mDocumentURI, loadContext, getter_AddRefs(principal));
2902 if (NS_SUCCEEDED(rv)) {
2903 SetPrincipals(principal, principal);
2904 }
2905 }
2906 }
2907
2908 if (mFontFaceSet) {
2909 mFontFaceSet->RefreshStandardFontLoadPrincipal();
2910 }
2911
2912 // Refresh the principal on the realm.
2913 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
2914 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal();
2915 }
2916 }
2917
MaybeDowngradePrincipal(nsIPrincipal * aPrincipal)2918 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal(
2919 nsIPrincipal* aPrincipal) {
2920 if (!aPrincipal) {
2921 return nullptr;
2922 }
2923
2924 // We can't load a document with an expanded principal. If we're given one,
2925 // automatically downgrade it to the last principal it subsumes (which is the
2926 // extension principal, in the case of extension content scripts).
2927 auto* basePrin = BasePrincipal::Cast(aPrincipal);
2928 if (basePrin->Is<ExpandedPrincipal>()) {
2929 MOZ_DIAGNOSTIC_ASSERT(false,
2930 "Should never try to create a document with "
2931 "an expanded principal");
2932
2933 auto* expanded = basePrin->As<ExpandedPrincipal>();
2934 return do_AddRef(expanded->AllowList().LastElement());
2935 }
2936
2937 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) {
2938 // We basically want the parent document here, but because this is very
2939 // early in the load, GetInProcessParentDocument() returns null, so we use
2940 // the docshell hierarchy to get this information instead.
2941 if (RefPtr<BrowsingContext> parent =
2942 mDocumentContainer->GetBrowsingContext()->GetParent()) {
2943 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow());
2944 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) {
2945 nsCOMPtr<nsIPrincipal> nullPrincipal =
2946 NullPrincipal::CreateWithoutOriginAttributes();
2947 return nullPrincipal.forget();
2948 }
2949 }
2950 }
2951 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
2952 return principal.forget();
2953 }
2954
FindDocStyleSheetInsertionPoint(const StyleSheet & aSheet)2955 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
2956 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
2957
2958 // lowest index first
2959 int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
2960
2961 size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
2962 size_t index = 0;
2963 for (; index < count; index++) {
2964 auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
2965 MOZ_ASSERT(sheet);
2966 int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
2967 if (sheetDocIndex > newDocIndex) {
2968 break;
2969 }
2970
2971 // If the sheet is not owned by the document it can be an author
2972 // sheet registered at nsStyleSheetService or an additional author
2973 // sheet on the document, which means the new
2974 // doc sheet should end up before it.
2975 if (sheetDocIndex < 0) {
2976 if (sheetService) {
2977 auto& authorSheets = *sheetService->AuthorStyleSheets();
2978 if (authorSheets.IndexOf(sheet) != authorSheets.NoIndex) {
2979 break;
2980 }
2981 }
2982 if (sheet == GetFirstAdditionalAuthorSheet()) {
2983 break;
2984 }
2985 }
2986 }
2987
2988 return index;
2989 }
2990
ResetStylesheetsToURI(nsIURI * aURI)2991 void Document::ResetStylesheetsToURI(nsIURI* aURI) {
2992 MOZ_ASSERT(aURI);
2993
2994 ClearAdoptedStyleSheets();
2995
2996 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) {
2997 for (auto& sheet : Reversed(aSheetList)) {
2998 sheet->ClearAssociatedDocumentOrShadowRoot();
2999 if (mStyleSetFilled) {
3000 mStyleSet->RemoveStyleSheet(*sheet);
3001 }
3002 }
3003 aSheetList.Clear();
3004 };
3005 ClearSheetList(mStyleSheets);
3006 for (auto& sheets : mAdditionalSheets) {
3007 ClearSheetList(sheets);
3008 }
3009 if (mStyleSetFilled) {
3010 if (auto* ss = nsStyleSheetService::GetInstance()) {
3011 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) {
3012 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot());
3013 if (sheet->IsApplicable()) {
3014 mStyleSet->RemoveStyleSheet(*sheet);
3015 }
3016 }
3017 }
3018 }
3019
3020 // Now reset our inline style and attribute sheets.
3021 if (mAttrStyleSheet) {
3022 mAttrStyleSheet->Reset();
3023 mAttrStyleSheet->SetOwningDocument(this);
3024 } else {
3025 mAttrStyleSheet = new nsHTMLStyleSheet(this);
3026 }
3027
3028 if (!mStyleAttrStyleSheet) {
3029 mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet();
3030 }
3031
3032 if (mStyleSetFilled) {
3033 FillStyleSetDocumentSheets();
3034
3035 if (mStyleSet->StyleSheetsHaveChanged()) {
3036 ApplicableStylesChanged();
3037 }
3038 }
3039 }
3040
AppendSheetsToStyleSet(ServoStyleSet * aStyleSet,const nsTArray<RefPtr<StyleSheet>> & aSheets)3041 static void AppendSheetsToStyleSet(
3042 ServoStyleSet* aStyleSet, const nsTArray<RefPtr<StyleSheet>>& aSheets) {
3043 for (StyleSheet* sheet : Reversed(aSheets)) {
3044 aStyleSet->AppendStyleSheet(*sheet);
3045 }
3046 }
3047
FillStyleSetUserAndUASheets()3048 void Document::FillStyleSetUserAndUASheets() {
3049 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt
3050 // ordering.
3051
3052 // The document will fill in the document sheets when we create the presshell
3053 auto* cache = GlobalStyleSheetCache::Singleton();
3054
3055 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3056 MOZ_ASSERT(sheetService,
3057 "should never be creating a StyleSet after the style sheet "
3058 "service has gone");
3059
3060 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
3061 mStyleSet->AppendStyleSheet(*sheet);
3062 }
3063
3064 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet()
3065 : cache->GetUserContentSheet();
3066 if (sheet) {
3067 mStyleSet->AppendStyleSheet(*sheet);
3068 }
3069
3070 mStyleSet->AppendStyleSheet(*cache->UASheet());
3071
3072 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) {
3073 mStyleSet->AppendStyleSheet(*cache->MathMLSheet());
3074 }
3075
3076 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) {
3077 mStyleSet->AppendStyleSheet(*cache->SVGSheet());
3078 }
3079
3080 mStyleSet->AppendStyleSheet(*cache->HTMLSheet());
3081
3082 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) {
3083 mStyleSet->AppendStyleSheet(*cache->NoFramesSheet());
3084 }
3085
3086 if (nsLayoutUtils::ShouldUseNoScriptSheet(this)) {
3087 mStyleSet->AppendStyleSheet(*cache->NoScriptSheet());
3088 }
3089
3090 mStyleSet->AppendStyleSheet(*cache->CounterStylesSheet());
3091
3092 // Load the minimal XUL rules for scrollbars and a few other XUL things
3093 // that non-XUL (typically HTML) documents commonly use.
3094 mStyleSet->AppendStyleSheet(*cache->MinimalXULSheet());
3095
3096 // Only load the full XUL sheet if we'll need it.
3097 if (LoadsFullXULStyleSheetUpFront()) {
3098 mStyleSet->AppendStyleSheet(*cache->XULSheet());
3099 }
3100
3101 mStyleSet->AppendStyleSheet(*cache->FormsSheet());
3102 mStyleSet->AppendStyleSheet(*cache->ScrollbarsSheet());
3103
3104 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
3105 mStyleSet->AppendStyleSheet(*sheet);
3106 }
3107
3108 MOZ_ASSERT(!mQuirkSheetAdded);
3109 if (NeedsQuirksSheet()) {
3110 mStyleSet->AppendStyleSheet(*cache->QuirkSheet());
3111 mQuirkSheetAdded = true;
3112 }
3113 }
3114
FillStyleSet()3115 void Document::FillStyleSet() {
3116 MOZ_ASSERT(!mStyleSetFilled);
3117 FillStyleSetUserAndUASheets();
3118 FillStyleSetDocumentSheets();
3119 mStyleSetFilled = true;
3120 }
3121
RemoveContentEditableStyleSheets()3122 void Document::RemoveContentEditableStyleSheets() {
3123 MOZ_ASSERT(IsHTMLOrXHTML());
3124
3125 auto* cache = GlobalStyleSheetCache::Singleton();
3126 bool changed = false;
3127 if (mDesignModeSheetAdded) {
3128 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3129 mDesignModeSheetAdded = false;
3130 changed = true;
3131 }
3132 if (mContentEditableSheetAdded) {
3133 mStyleSet->RemoveStyleSheet(*cache->ContentEditableSheet());
3134 mContentEditableSheetAdded = false;
3135 changed = true;
3136 }
3137 if (changed) {
3138 MOZ_ASSERT(mStyleSetFilled);
3139 ApplicableStylesChanged();
3140 }
3141 }
3142
AddContentEditableStyleSheetsToStyleSet(bool aDesignMode)3143 void Document::AddContentEditableStyleSheetsToStyleSet(bool aDesignMode) {
3144 MOZ_ASSERT(IsHTMLOrXHTML());
3145 MOZ_DIAGNOSTIC_ASSERT(mStyleSetFilled,
3146 "Caller should ensure we're being rendered");
3147
3148 auto* cache = GlobalStyleSheetCache::Singleton();
3149 bool changed = false;
3150 if (!mContentEditableSheetAdded) {
3151 mStyleSet->AppendStyleSheet(*cache->ContentEditableSheet());
3152 mContentEditableSheetAdded = true;
3153 changed = true;
3154 }
3155 if (mDesignModeSheetAdded != aDesignMode) {
3156 if (mDesignModeSheetAdded) {
3157 mStyleSet->RemoveStyleSheet(*cache->DesignModeSheet());
3158 } else {
3159 mStyleSet->AppendStyleSheet(*cache->DesignModeSheet());
3160 }
3161 mDesignModeSheetAdded = !mDesignModeSheetAdded;
3162 changed = true;
3163 }
3164 if (changed) {
3165 ApplicableStylesChanged();
3166 }
3167 }
3168
FillStyleSetDocumentSheets()3169 void Document::FillStyleSetDocumentSheets() {
3170 MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
3171 "Style set already has document sheets?");
3172
3173 // Sheets are added in reverse order to avoid worst-case time complexity when
3174 // looking up the index of a sheet.
3175 //
3176 // Note that usually appending is faster (rebuilds less stuff in the
3177 // styleset), but in this case it doesn't matter since we're filling the
3178 // styleset from scratch anyway.
3179 for (StyleSheet* sheet : Reversed(mStyleSheets)) {
3180 if (sheet->IsApplicable()) {
3181 mStyleSet->AddDocStyleSheet(*sheet);
3182 }
3183 }
3184
3185 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) {
3186 if (aSheet.IsApplicable()) {
3187 mStyleSet->AddDocStyleSheet(aSheet);
3188 }
3189 });
3190
3191 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
3192 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
3193 mStyleSet->AppendStyleSheet(*sheet);
3194 }
3195
3196 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet]);
3197 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet]);
3198 AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAuthorSheet]);
3199 }
3200
CompatibilityModeChanged()3201 void Document::CompatibilityModeChanged() {
3202 MOZ_ASSERT(IsHTMLOrXHTML());
3203 CSSLoader()->SetCompatibilityMode(mCompatMode);
3204 mStyleSet->CompatibilityModeChanged();
3205 if (PresShell* presShell = GetPresShell()) {
3206 // Selectors may have become case-sensitive / case-insensitive, the stylist
3207 // has already performed the relevant invalidation.
3208 presShell->EnsureStyleFlush();
3209 }
3210 if (!mStyleSetFilled) {
3211 MOZ_ASSERT(!mQuirkSheetAdded);
3212 return;
3213 }
3214 if (mQuirkSheetAdded == NeedsQuirksSheet()) {
3215 return;
3216 }
3217 auto* cache = GlobalStyleSheetCache::Singleton();
3218 StyleSheet* sheet = cache->QuirkSheet();
3219 if (mQuirkSheetAdded) {
3220 mStyleSet->RemoveStyleSheet(*sheet);
3221 } else {
3222 mStyleSet->AppendStyleSheet(*sheet);
3223 }
3224 mQuirkSheetAdded = !mQuirkSheetAdded;
3225 ApplicableStylesChanged();
3226 }
3227
SetCompatibilityMode(nsCompatibility aMode)3228 void Document::SetCompatibilityMode(nsCompatibility aMode) {
3229 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
3230 "Bad compat mode for XHTML document!");
3231
3232 if (mCompatMode == aMode) {
3233 return;
3234 }
3235 mCompatMode = aMode;
3236 CompatibilityModeChanged();
3237 // Trigger recomputation of the nsViewportInfo the next time it's queried.
3238 mViewportType = Unknown;
3239 }
3240
WarnIfSandboxIneffective(nsIDocShell * aDocShell,uint32_t aSandboxFlags,nsIChannel * aChannel)3241 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell,
3242 uint32_t aSandboxFlags,
3243 nsIChannel* aChannel) {
3244 // If the document permits allow-top-navigation and
3245 // allow-top-navigation-by-user-activation this will permit all top
3246 // navigation.
3247 if (aSandboxFlags != SANDBOXED_NONE &&
3248 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) &&
3249 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) {
3250 nsContentUtils::ReportToConsole(
3251 nsIScriptError::warningFlag, "Iframe Sandbox"_ns,
3252 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES,
3253 "BothAllowTopNavigationAndUserActivationPresent");
3254 }
3255 // If the document is sandboxed (via the HTML5 iframe sandbox
3256 // attribute) and both the allow-scripts and allow-same-origin
3257 // keywords are supplied, the sandboxed document can call into its
3258 // parent document and remove its sandboxing entirely - we print a
3259 // warning to the web console in this case.
3260 if (aSandboxFlags & SANDBOXED_NAVIGATION &&
3261 !(aSandboxFlags & SANDBOXED_SCRIPTS) &&
3262 !(aSandboxFlags & SANDBOXED_ORIGIN)) {
3263 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext();
3264 MOZ_ASSERT(bc->IsInProcess());
3265
3266 RefPtr<BrowsingContext> parentBC = bc->GetParent();
3267 if (!parentBC || !parentBC->IsInProcess()) {
3268 // If parent document is not in process, then by construction it
3269 // cannot be same origin.
3270 return;
3271 }
3272
3273 // Don't warn if our parent is not the top-level document.
3274 if (!parentBC->IsTopContent()) {
3275 return;
3276 }
3277
3278 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell();
3279 MOZ_ASSERT(parentDocShell);
3280
3281 nsCOMPtr<nsIChannel> parentChannel;
3282 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
3283 if (!parentChannel) {
3284 return;
3285 }
3286 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
3287 if (NS_FAILED(rv)) {
3288 return;
3289 }
3290
3291 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument();
3292 nsCOMPtr<nsIURI> iframeUri;
3293 parentChannel->GetURI(getter_AddRefs(iframeUri));
3294 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3295 "Iframe Sandbox"_ns, parentDocument,
3296 nsContentUtils::eSECURITY_PROPERTIES,
3297 "BothAllowScriptsAndSameOriginPresent",
3298 nsTArray<nsString>(), iframeUri);
3299 }
3300 }
3301
IsSynthesized()3302 bool Document::IsSynthesized() {
3303 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
3304 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized();
3305 }
3306
3307 // static
IsCallerChromeOrAddon(JSContext * aCx,JSObject * aObject)3308 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) {
3309 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
3310 return principal && (principal->IsSystemPrincipal() ||
3311 principal->GetIsAddonOrExpandedAddonPrincipal());
3312 }
3313
StartDocumentLoad(const char * aCommand,nsIChannel * aChannel,nsILoadGroup * aLoadGroup,nsISupports * aContainer,nsIStreamListener ** aDocListener,bool aReset)3314 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
3315 nsILoadGroup* aLoadGroup,
3316 nsISupports* aContainer,
3317 nsIStreamListener** aDocListener,
3318 bool aReset) {
3319 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
3320 nsCOMPtr<nsIURI> uri;
3321 aChannel->GetURI(getter_AddRefs(uri));
3322 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
3323 ("DOCUMENT %p StartDocumentLoad %s", this,
3324 uri ? uri->GetSpecOrDefault().get() : ""));
3325 }
3326
3327 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
3328 "Bad readyState");
3329 SetReadyStateInternal(READYSTATE_LOADING);
3330
3331 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
3332 mLoadedAsData = true;
3333 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true);
3334 // We need to disable script & style loading in this case.
3335 // We leave them disabled even in EndLoad(), and let anyone
3336 // who puts the document on display to worry about enabling.
3337
3338 // Do not load/process scripts when loading as data
3339 ScriptLoader()->SetEnabled(false);
3340
3341 // styles
3342 CSSLoader()->SetEnabled(
3343 false); // Do not load/process styles when loading as data
3344 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) {
3345 // Allow CSS, but not scripts
3346 ScriptLoader()->SetEnabled(false);
3347 }
3348
3349 mMayStartLayout = false;
3350 MOZ_ASSERT(!mReadyForIdle,
3351 "We should never hit DOMContentLoaded before this point");
3352
3353 if (aReset) {
3354 Reset(aChannel, aLoadGroup);
3355 }
3356
3357 nsAutoCString contentType;
3358 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
3359 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns,
3360 contentType))) ||
3361 NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
3362 // XXX this is only necessary for viewsource:
3363 nsACString::const_iterator start, end, semicolon;
3364 contentType.BeginReading(start);
3365 contentType.EndReading(end);
3366 semicolon = start;
3367 FindCharInReadable(';', semicolon, end);
3368 SetContentType(Substring(start, semicolon));
3369 }
3370
3371 RetrieveRelevantHeaders(aChannel);
3372
3373 mChannel = aChannel;
3374 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
3375 if (inStrmChan) {
3376 bool isSrcdocChannel;
3377 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
3378 if (isSrcdocChannel) {
3379 mIsSrcdocDocument = true;
3380 }
3381 }
3382
3383 if (mChannel) {
3384 nsLoadFlags loadFlags;
3385 mChannel->GetLoadFlags(&loadFlags);
3386 bool isDocument = false;
3387 mChannel->GetIsDocument(&isDocument);
3388 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument &&
3389 IsSynthesized() && XRE_IsContentProcess()) {
3390 ContentChild::UpdateCookieStatus(mChannel);
3391 }
3392
3393 // Store the security info for future use.
3394 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
3395 }
3396
3397 // If this document is being loaded by a docshell, copy its sandbox flags
3398 // to the document, and store the fullscreen enabled flag. These are
3399 // immutable after being set here.
3400 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer);
3401
3402 // If this is an error page, don't inherit sandbox flags
3403 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3404 if (docShell && !loadInfo->GetLoadErrorPage()) {
3405 mSandboxFlags = loadInfo->GetSandboxFlags();
3406 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
3407 }
3408
3409 // Set the opener policy for the top level content document.
3410 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel);
3411 nsILoadInfo::CrossOriginOpenerPolicy policy =
3412 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
3413 if (IsTopLevelContentDocument() && httpChan &&
3414 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell &&
3415 docShell->GetBrowsingContext()) {
3416 // Setting the opener policy on a discarded context has no effect.
3417 Unused << docShell->GetBrowsingContext()->SetOpenerPolicy(policy);
3418 }
3419
3420 // The CSP directives upgrade-insecure-requests as well as
3421 // block-all-mixed-content not only apply to the toplevel document,
3422 // but also to nested documents. The loadInfo of a subdocument
3423 // load already holds the correct flag, so let's just set it here
3424 // on the document. Please note that we set the appropriate preload
3425 // bits just for the sake of completeness here, because the preloader
3426 // does not reach into subdocuments.
3427 mUpgradeInsecureRequests = loadInfo->GetUpgradeInsecureRequests();
3428 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3429 mBlockAllMixedContent = loadInfo->GetBlockAllMixedContent();
3430 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3431
3432 // HTTPS-Only Mode flags
3433 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all
3434 // sub-resources and sub-documents.
3435 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
3436
3437 nsresult rv = InitReferrerInfo(aChannel);
3438 NS_ENSURE_SUCCESS(rv, rv);
3439
3440 rv = InitCOEP(aChannel);
3441 NS_ENSURE_SUCCESS(rv, rv);
3442
3443 // Check CSP navigate-to
3444 // We need to enforce the CSP of the document that initiated the load,
3445 // which is the CSP to inherit.
3446 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
3447 if (cspToInherit) {
3448 bool allowsNavigateTo = false;
3449 rv = cspToInherit->GetAllowsNavigateTo(
3450 mDocumentURI, loadInfo->GetIsFormSubmission(),
3451 !loadInfo->RedirectChain().IsEmpty(), /* aWasRedirected */
3452 true, /* aEnforceWhitelist */
3453 &allowsNavigateTo);
3454 NS_ENSURE_SUCCESS(rv, rv);
3455
3456 if (!allowsNavigateTo) {
3457 aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
3458 return NS_OK;
3459 }
3460 }
3461
3462 rv = InitCSP(aChannel);
3463 NS_ENSURE_SUCCESS(rv, rv);
3464
3465 // Initialize FeaturePolicy
3466 rv = InitFeaturePolicy(aChannel);
3467 NS_ENSURE_SUCCESS(rv, rv);
3468
3469 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
3470 NS_ENSURE_SUCCESS(rv, rv);
3471
3472 // Generally XFO and CSP frame-ancestors is handled within
3473 // DocumentLoadListener. However, the DocumentLoadListener can not handle
3474 // object and embed. Until then we have to enforce it here (See Bug 1646899).
3475 nsContentPolicyType internalContentType =
3476 loadInfo->InternalContentPolicyType();
3477 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT ||
3478 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) {
3479 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel);
3480
3481 nsresult status;
3482 aChannel->GetStatus(&status);
3483 if (status == NS_ERROR_XFO_VIOLATION) {
3484 // stop! ERROR page!
3485 // But before we have to reset the principal of the document
3486 // because the onload() event fires before the error page
3487 // is displayed and we do not want the enclosing document
3488 // to access the contentDocument.
3489 RefPtr<NullPrincipal> nullPrincipal =
3490 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal());
3491 // Before calling SetPrincipals() we should ensure that mFontFaceSet
3492 // and also GetInnerWindow() is still null at this point, before
3493 // we can fix Bug 1614735: Evaluate calls to SetPrincipal
3494 // within Document.cpp
3495 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow());
3496 SetPrincipals(nullPrincipal, nullPrincipal);
3497 }
3498 }
3499
3500 return NS_OK;
3501 }
3502
SetLoadedAsData(bool aLoadedAsData,bool aConsiderForMemoryReporting)3503 void Document::SetLoadedAsData(bool aLoadedAsData,
3504 bool aConsiderForMemoryReporting) {
3505 mLoadedAsData = aLoadedAsData;
3506 if (aConsiderForMemoryReporting) {
3507 nsIGlobalObject* global = GetScopeObject();
3508 if (global) {
3509 if (nsPIDOMWindowInner* window = global->AsInnerWindow()) {
3510 nsGlobalWindowInner::Cast(window)
3511 ->RegisterDataDocumentForMemoryReporting(this);
3512 }
3513 }
3514 }
3515 }
3516
GetCsp() const3517 nsIContentSecurityPolicy* Document::GetCsp() const { return mCSP; }
3518
SetCsp(nsIContentSecurityPolicy * aCSP)3519 void Document::SetCsp(nsIContentSecurityPolicy* aCSP) { mCSP = aCSP; }
3520
GetPreloadCsp() const3521 nsIContentSecurityPolicy* Document::GetPreloadCsp() const {
3522 return mPreloadCSP;
3523 }
3524
SetPreloadCsp(nsIContentSecurityPolicy * aPreloadCSP)3525 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) {
3526 mPreloadCSP = aPreloadCSP;
3527 }
3528
GetCspJSON(nsString & aJSON)3529 void Document::GetCspJSON(nsString& aJSON) {
3530 aJSON.Truncate();
3531
3532 if (!mCSP) {
3533 dom::CSPPolicies jsonPolicies;
3534 jsonPolicies.ToJSON(aJSON);
3535 return;
3536 }
3537 mCSP->ToJSON(aJSON);
3538 }
3539
SendToConsole(nsCOMArray<nsISecurityConsoleMessage> & aMessages)3540 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
3541 for (uint32_t i = 0; i < aMessages.Length(); ++i) {
3542 nsAutoString messageTag;
3543 aMessages[i]->GetTag(messageTag);
3544
3545 nsAutoString category;
3546 aMessages[i]->GetCategory(category);
3547
3548 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
3549 NS_ConvertUTF16toUTF8(category), this,
3550 nsContentUtils::eSECURITY_PROPERTIES,
3551 NS_ConvertUTF16toUTF8(messageTag).get());
3552 }
3553 }
3554
ApplySettingsFromCSP(bool aSpeculative)3555 void Document::ApplySettingsFromCSP(bool aSpeculative) {
3556 nsresult rv = NS_OK;
3557 if (!aSpeculative) {
3558 // 1) apply settings from regular CSP
3559 if (mCSP) {
3560 // Set up 'block-all-mixed-content' if not already inherited
3561 // from the parent context or set by any other CSP.
3562 if (!mBlockAllMixedContent) {
3563 bool block = false;
3564 rv = mCSP->GetBlockAllMixedContent(&block);
3565 NS_ENSURE_SUCCESS_VOID(rv);
3566 mBlockAllMixedContent = block;
3567 }
3568 if (!mBlockAllMixedContentPreloads) {
3569 mBlockAllMixedContentPreloads = mBlockAllMixedContent;
3570 }
3571
3572 // Set up 'upgrade-insecure-requests' if not already inherited
3573 // from the parent context or set by any other CSP.
3574 if (!mUpgradeInsecureRequests) {
3575 bool upgrade = false;
3576 rv = mCSP->GetUpgradeInsecureRequests(&upgrade);
3577 NS_ENSURE_SUCCESS_VOID(rv);
3578 mUpgradeInsecureRequests = upgrade;
3579 }
3580 if (!mUpgradeInsecurePreloads) {
3581 mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
3582 }
3583 // Update csp settings in the parent process
3584 if (auto* wgc = GetWindowGlobalChild()) {
3585 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent,
3586 mUpgradeInsecureRequests);
3587 }
3588 }
3589 return;
3590 }
3591
3592 // 2) apply settings from speculative csp
3593 if (mPreloadCSP) {
3594 if (!mBlockAllMixedContentPreloads) {
3595 bool block = false;
3596 rv = mPreloadCSP->GetBlockAllMixedContent(&block);
3597 NS_ENSURE_SUCCESS_VOID(rv);
3598 mBlockAllMixedContent = block;
3599 }
3600 if (!mUpgradeInsecurePreloads) {
3601 bool upgrade = false;
3602 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade);
3603 NS_ENSURE_SUCCESS_VOID(rv);
3604 mUpgradeInsecurePreloads = upgrade;
3605 }
3606 }
3607 }
3608
InitCSP(nsIChannel * aChannel)3609 nsresult Document::InitCSP(nsIChannel* aChannel) {
3610 MOZ_ASSERT(!mScriptGlobalObject,
3611 "CSP must be initialized before mScriptGlobalObject is set!");
3612
3613 // If this is a data document - no need to set CSP.
3614 if (mLoadedAsData) {
3615 return NS_OK;
3616 }
3617
3618 // If this is an image, no need to set a CSP. Otherwise SVG images
3619 // served with a CSP might block internally applied inline styles.
3620 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
3621 if (loadInfo->GetExternalContentPolicyType() ==
3622 ExtContentPolicy::TYPE_IMAGE) {
3623 return NS_OK;
3624 }
3625
3626 MOZ_ASSERT(!mCSP, "where did mCSP get set if not here?");
3627
3628 // If there is a CSP that needs to be inherited from whatever
3629 // global is considered the client of the document fetch then
3630 // we query it here from the loadinfo in case the newly created
3631 // document needs to inherit the CSP. See:
3632 // https://w3c.github.io/webappsec-csp/#initialize-document-csp
3633 bool inheritedCSP = CSP_ShouldResponseInheritCSP(aChannel);
3634 if (inheritedCSP) {
3635 mCSP = loadInfo->GetCspToInherit();
3636 }
3637
3638 // If there is no CSP to inherit, then we create a new CSP here so
3639 // that history entries always have the right reference in case a
3640 // Meta CSP gets dynamically added after the history entry has
3641 // already been created.
3642 if (!mCSP) {
3643 mCSP = new nsCSPContext();
3644 }
3645
3646 // Always overwrite the requesting context of the CSP so that any new
3647 // 'self' keyword added to an inherited CSP translates correctly.
3648 nsresult rv = mCSP->SetRequestContextWithDocument(this);
3649 if (NS_WARN_IF(NS_FAILED(rv))) {
3650 return rv;
3651 }
3652
3653 nsAutoCString tCspHeaderValue, tCspROHeaderValue;
3654
3655 nsCOMPtr<nsIHttpChannel> httpChannel;
3656 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3657 if (NS_WARN_IF(NS_FAILED(rv))) {
3658 return rv;
3659 }
3660
3661 if (httpChannel) {
3662 Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
3663 tCspHeaderValue);
3664
3665 Unused << httpChannel->GetResponseHeader(
3666 "content-security-policy-report-only"_ns, tCspROHeaderValue);
3667 }
3668 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
3669 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
3670
3671 // Check if this is a document from a WebExtension.
3672 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
3673 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
3674
3675 // If there's no CSP to apply, go ahead and return early
3676 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() &&
3677 cspROHeaderValue.IsEmpty()) {
3678 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
3679 nsCOMPtr<nsIURI> chanURI;
3680 aChannel->GetURI(getter_AddRefs(chanURI));
3681 nsAutoCString aspec;
3682 chanURI->GetAsciiSpec(aspec);
3683 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3684 ("no CSP for document, %s", aspec.get()));
3685 }
3686
3687 return NS_OK;
3688 }
3689
3690 MOZ_LOG(gCspPRLog, LogLevel::Debug,
3691 ("Document is an add-on or CSP header specified %p", this));
3692
3693 // ----- if the doc is an addon, apply its CSP.
3694 if (addonPolicy) {
3695 mCSP->AppendPolicy(addonPolicy->BaseCSP(), false, false);
3696
3697 mCSP->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
3698 // Bug 1548468: Move CSP off ExpandedPrincipal
3699 // Currently the LoadInfo holds the source of truth for every resource load
3700 // because LoadInfo::GetCSP() queries the CSP from an ExpandedPrincipal
3701 // (and not from the Client) if the load was triggered by an extension.
3702 auto* basePrin = BasePrincipal::Cast(principal);
3703 if (basePrin->Is<ExpandedPrincipal>()) {
3704 basePrin->As<ExpandedPrincipal>()->SetCsp(mCSP);
3705 }
3706 }
3707
3708 // ----- if there's a full-strength CSP header, apply it.
3709 if (!cspHeaderValue.IsEmpty()) {
3710 mHasCSPDeliveredThroughHeader = true;
3711 rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false);
3712 NS_ENSURE_SUCCESS(rv, rv);
3713 }
3714
3715 // ----- if there's a report-only CSP header, apply it.
3716 if (!cspROHeaderValue.IsEmpty()) {
3717 rv = CSP_AppendCSPFromHeader(mCSP, cspROHeaderValue, true);
3718 NS_ENSURE_SUCCESS(rv, rv);
3719 }
3720
3721 // ----- Enforce sandbox policy if supplied in CSP header
3722 // The document may already have some sandbox flags set (e.g. if the document
3723 // is an iframe with the sandbox attribute set). If we have a CSP sandbox
3724 // directive, intersect the CSP sandbox flags with the existing flags. This
3725 // corresponds to the _least_ permissive policy.
3726 uint32_t cspSandboxFlags = SANDBOXED_NONE;
3727 rv = mCSP->GetCSPSandboxFlags(&cspSandboxFlags);
3728 NS_ENSURE_SUCCESS(rv, rv);
3729
3730 // Probably the iframe sandbox attribute already caused the creation of a
3731 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
3732 // and no one has been created yet.
3733 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
3734 !(mSandboxFlags & SANDBOXED_ORIGIN);
3735
3736 mSandboxFlags |= cspSandboxFlags;
3737
3738 if (needNewNullPrincipal) {
3739 principal = NullPrincipal::CreateWithInheritedAttributes(principal);
3740 // Skip setting the content blocking allowlist principal to NullPrincipal.
3741 // The principal is only used to enable/disable trackingprotection via
3742 // permission and can be shared with the top level sandboxed site.
3743 // See Bug 1654546.
3744 SetPrincipals(principal, principal);
3745 }
3746
3747 ApplySettingsFromCSP(false);
3748 return NS_OK;
3749 }
3750
GetParentFeaturePolicy()3751 already_AddRefed<dom::FeaturePolicy> Document::GetParentFeaturePolicy() {
3752 BrowsingContext* browsingContext = GetBrowsingContext();
3753 if (!browsingContext) {
3754 return nullptr;
3755 }
3756 if (!browsingContext->IsContentSubframe()) {
3757 return nullptr;
3758 }
3759
3760 HTMLIFrameElement* iframe =
3761 HTMLIFrameElement::FromNodeOrNull(browsingContext->GetEmbedderElement());
3762 if (iframe) {
3763 return do_AddRef(iframe->FeaturePolicy());
3764 }
3765
3766 if (XRE_IsParentProcess()) {
3767 return do_AddRef(browsingContext->Canonical()->GetContainerFeaturePolicy());
3768 }
3769
3770 WindowContext* windowContext = browsingContext->GetCurrentWindowContext();
3771 if (!windowContext) {
3772 return nullptr;
3773 }
3774
3775 WindowGlobalChild* child = windowContext->GetWindowGlobalChild();
3776 if (!child) {
3777 return nullptr;
3778 }
3779
3780 return do_AddRef(child->GetContainerFeaturePolicy());
3781 }
3782
InitFeaturePolicy(nsIChannel * aChannel)3783 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
3784 MOZ_ASSERT(mFeaturePolicy, "we should only call init once");
3785
3786 mFeaturePolicy->ResetDeclaredPolicy();
3787
3788 mFeaturePolicy->SetDefaultOrigin(NodePrincipal());
3789
3790 RefPtr<mozilla::dom::FeaturePolicy> parentPolicy = GetParentFeaturePolicy();
3791 if (parentPolicy) {
3792 // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
3793 mFeaturePolicy->InheritPolicy(parentPolicy);
3794 mFeaturePolicy->SetSrcOrigin(parentPolicy->GetSrcOrigin());
3795 }
3796
3797 // We don't want to parse the http Feature-Policy header if this pref is off.
3798 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
3799 return NS_OK;
3800 }
3801
3802 nsCOMPtr<nsIHttpChannel> httpChannel;
3803 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3804 if (NS_WARN_IF(NS_FAILED(rv))) {
3805 return rv;
3806 }
3807
3808 if (!httpChannel) {
3809 return NS_OK;
3810 }
3811
3812 // query the policy from the header
3813 nsAutoCString value;
3814 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value);
3815 if (NS_SUCCEEDED(rv)) {
3816 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value),
3817 NodePrincipal(), nullptr);
3818 }
3819
3820 return NS_OK;
3821 }
3822
InitReferrerInfo(nsIChannel * aChannel)3823 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) {
3824 MOZ_ASSERT(mReferrerInfo);
3825 MOZ_ASSERT(mPreloadReferrerInfo);
3826
3827 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) {
3828 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with
3829 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless
3830 // they have an opaque origin.
3831 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
3832 if (BrowsingContext* bc = GetBrowsingContext()) {
3833 // At this point the document is not fully created and mParentDocument has
3834 // not been set yet,
3835 Document* parentDoc = bc->GetEmbedderElement()
3836 ? bc->GetEmbedderElement()->OwnerDoc()
3837 : nullptr;
3838 if (parentDoc) {
3839 mReferrerInfo = parentDoc->GetReferrerInfo();
3840 mPreloadReferrerInfo = mReferrerInfo;
3841 return NS_OK;
3842 }
3843
3844 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(),
3845 "srcdoc without null principal as toplevel!");
3846 }
3847 }
3848
3849 nsCOMPtr<nsIHttpChannel> httpChannel;
3850 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3851 if (NS_WARN_IF(NS_FAILED(rv))) {
3852 return rv;
3853 }
3854
3855 if (!httpChannel) {
3856 return NS_OK;
3857 }
3858
3859 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
3860 if (referrerInfo) {
3861 mReferrerInfo = referrerInfo;
3862 }
3863
3864 // Override policy if we get one from Referrerr-Policy header
3865 mozilla::dom::ReferrerPolicy policy =
3866 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
3867 mReferrerInfo = static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())
3868 ->CloneWithNewPolicy(policy);
3869
3870 mPreloadReferrerInfo = mReferrerInfo;
3871 return NS_OK;
3872 }
3873
InitCOEP(nsIChannel * aChannel)3874 nsresult Document::InitCOEP(nsIChannel* aChannel) {
3875 nsCOMPtr<nsIHttpChannel> httpChannel;
3876 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
3877 if (NS_FAILED(rv)) {
3878 return NS_OK;
3879 }
3880
3881 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel);
3882
3883 if (!intChannel) {
3884 return NS_OK;
3885 }
3886
3887 nsILoadInfo::CrossOriginEmbedderPolicy policy =
3888 nsILoadInfo::EMBEDDER_POLICY_NULL;
3889 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy(&policy))) {
3890 mEmbedderPolicy = Some(policy);
3891 }
3892
3893 return NS_OK;
3894 }
3895
StopDocumentLoad()3896 void Document::StopDocumentLoad() {
3897 if (mParser) {
3898 mParserAborted = true;
3899 mParser->Terminate();
3900 }
3901 }
3902
SetDocumentURI(nsIURI * aURI)3903 void Document::SetDocumentURI(nsIURI* aURI) {
3904 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
3905 mDocumentURI = aURI;
3906 nsIURI* newBase = GetDocBaseURI();
3907
3908 mDocURISchemeIsChrome = aURI && IsChromeURI(aURI);
3909
3910 bool equalBases = false;
3911 // Changing just the ref of a URI does not change how relative URIs would
3912 // resolve wrt to it, so we can treat the bases as equal as long as they're
3913 // equal ignoring the ref.
3914 if (oldBase && newBase) {
3915 oldBase->EqualsExceptRef(newBase, &equalBases);
3916 } else {
3917 equalBases = !oldBase && !newBase;
3918 }
3919
3920 // If this is the first time we're setting the document's URI, set the
3921 // document's original URI.
3922 if (!mOriginalURI) mOriginalURI = mDocumentURI;
3923
3924 // If changing the document's URI changed the base URI of the document, we
3925 // need to refresh the hrefs of all the links on the page.
3926 if (!equalBases) {
3927 RefreshLinkHrefs();
3928 }
3929
3930 // Recalculate our base domain
3931 mBaseDomain.Truncate();
3932 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
3933 if (thirdPartyUtil) {
3934 Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
3935 }
3936
3937 // Tell our WindowGlobalParent that the document's URI has been changed.
3938 nsPIDOMWindowInner* inner = GetInnerWindow();
3939 if (inner && inner->GetWindowGlobalChild()) {
3940 inner->GetWindowGlobalChild()->SetDocumentURI(mDocumentURI);
3941 }
3942 }
3943
GetFormattedTimeString(PRTime aTime,nsAString & aFormattedTimeString)3944 static void GetFormattedTimeString(PRTime aTime,
3945 nsAString& aFormattedTimeString) {
3946 PRExplodedTime prtime;
3947 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
3948 // "MM/DD/YYYY hh:mm:ss"
3949 char formatedTime[24];
3950 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
3951 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
3952 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) {
3953 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
3954 } else {
3955 // If we for whatever reason failed to find the last modified time
3956 // (or even the current time), fall back to what NS4.x returned.
3957 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00");
3958 }
3959 }
3960
GetLastModified(nsAString & aLastModified) const3961 void Document::GetLastModified(nsAString& aLastModified) const {
3962 if (!mLastModified.IsEmpty()) {
3963 aLastModified.Assign(mLastModified);
3964 } else {
3965 GetFormattedTimeString(PR_Now(), aLastModified);
3966 }
3967 }
3968
IncrementExpandoGeneration(Document & aDoc)3969 static void IncrementExpandoGeneration(Document& aDoc) {
3970 ++aDoc.mExpandoAndGeneration.generation;
3971 }
3972
AddToNameTable(Element * aElement,nsAtom * aName)3973 void Document::AddToNameTable(Element* aElement, nsAtom* aName) {
3974 MOZ_ASSERT(
3975 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
3976 "Only put elements that need to be exposed as document['name'] in "
3977 "the named table.");
3978
3979 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName);
3980
3981 // Null for out-of-memory
3982 if (entry) {
3983 if (!entry->HasNameElement() &&
3984 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
3985 IncrementExpandoGeneration(*this);
3986 }
3987 entry->AddNameElement(this, aElement);
3988 }
3989 }
3990
RemoveFromNameTable(Element * aElement,nsAtom * aName)3991 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) {
3992 // Speed up document teardown
3993 if (mIdentifierMap.Count() == 0) return;
3994
3995 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName);
3996 if (!entry) // Could be false if the element was anonymous, hence never added
3997 return;
3998
3999 entry->RemoveNameElement(aElement);
4000 if (!entry->HasNameElement() &&
4001 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4002 IncrementExpandoGeneration(*this);
4003 }
4004 }
4005
AddToIdTable(Element * aElement,nsAtom * aId)4006 void Document::AddToIdTable(Element* aElement, nsAtom* aId) {
4007 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
4008
4009 if (entry) { /* True except on OOM */
4010 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4011 !entry->HasNameElement() &&
4012 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4013 IncrementExpandoGeneration(*this);
4014 }
4015 entry->AddIdElement(aElement);
4016 }
4017 }
4018
RemoveFromIdTable(Element * aElement,nsAtom * aId)4019 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
4020 NS_ASSERTION(aId, "huhwhatnow?");
4021
4022 // Speed up document teardown
4023 if (mIdentifierMap.Count() == 0) {
4024 return;
4025 }
4026
4027 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
4028 if (!entry) // Can be null for XML elements with changing ids.
4029 return;
4030
4031 entry->RemoveIdElement(aElement);
4032 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
4033 !entry->HasNameElement() &&
4034 !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
4035 IncrementExpandoGeneration(*this);
4036 }
4037 if (entry->IsEmpty()) {
4038 mIdentifierMap.RemoveEntry(entry);
4039 }
4040 }
4041
UpdateReferrerInfoFromMeta(const nsAString & aMetaReferrer,bool aPreload)4042 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer,
4043 bool aPreload) {
4044 ReferrerPolicyEnum policy =
4045 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer);
4046 // The empty string "" corresponds to no referrer policy, causing a fallback
4047 // to a referrer policy defined elsewhere.
4048 if (policy == ReferrerPolicy::_empty) {
4049 return;
4050 }
4051
4052 MOZ_ASSERT(mReferrerInfo);
4053 MOZ_ASSERT(mPreloadReferrerInfo);
4054
4055 if (aPreload) {
4056 mPreloadReferrerInfo =
4057 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get())
4058 ->CloneWithNewPolicy(policy);
4059 } else {
4060 mReferrerInfo =
4061 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get())
4062 ->CloneWithNewPolicy(policy);
4063 }
4064 }
4065
SetPrincipals(nsIPrincipal * aNewPrincipal,nsIPrincipal * aNewPartitionedPrincipal)4066 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
4067 nsIPrincipal* aNewPartitionedPrincipal) {
4068 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal);
4069 if (aNewPrincipal && mAllowDNSPrefetch &&
4070 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) {
4071 if (aNewPrincipal->SchemeIs("https")) {
4072 mAllowDNSPrefetch = false;
4073 }
4074 }
4075
4076 mCSSLoader->DeregisterFromSheetCache();
4077
4078 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
4079 mPartitionedPrincipal = aNewPartitionedPrincipal;
4080
4081 mCSSLoader->RegisterInSheetCache();
4082
4083 #ifdef DEBUG
4084 // Validate that the docgroup is set correctly by calling its getter and
4085 // triggering its sanity check.
4086 //
4087 // If we're setting the principal to null, we don't want to perform the check,
4088 // as the document is entering an intermediate state where it does not have a
4089 // principal. It will be given another real principal shortly which we will
4090 // check. It's not unsafe to have a document which has a null principal in the
4091 // same docgroup as another document, so this should not be a problem.
4092 if (aNewPrincipal) {
4093 GetDocGroup();
4094 }
4095 #endif
4096 }
4097
4098 #ifdef DEBUG
AssertDocGroupMatchesKey() const4099 void Document::AssertDocGroupMatchesKey() const {
4100 // Sanity check that we have an up-to-date and accurate docgroup
4101 // We only check if the principal when we can get the browsing context.
4102 if (!GetBrowsingContext()) {
4103 return;
4104 }
4105
4106 if (mDocGroup) {
4107 nsAutoCString docGroupKey;
4108
4109 // GetKey() can fail, e.g. after the TLD service has shut down.
4110 nsresult rv = mozilla::dom::DocGroup::GetKey(
4111 NodePrincipal(), CrossOriginIsolated(), docGroupKey);
4112 if (NS_SUCCEEDED(rv)) {
4113 MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
4114 }
4115 }
4116 }
4117 #endif
4118
Dispatch(TaskCategory aCategory,already_AddRefed<nsIRunnable> && aRunnable)4119 nsresult Document::Dispatch(TaskCategory aCategory,
4120 already_AddRefed<nsIRunnable>&& aRunnable) {
4121 // Note that this method may be called off the main thread.
4122 if (mDocGroup) {
4123 return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
4124 }
4125 return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
4126 }
4127
EventTargetFor(TaskCategory aCategory) const4128 nsISerialEventTarget* Document::EventTargetFor(TaskCategory aCategory) const {
4129 if (mDocGroup) {
4130 return mDocGroup->EventTargetFor(aCategory);
4131 }
4132 return DispatcherTrait::EventTargetFor(aCategory);
4133 }
4134
AbstractMainThreadFor(mozilla::TaskCategory aCategory)4135 AbstractThread* Document::AbstractMainThreadFor(
4136 mozilla::TaskCategory aCategory) {
4137 MOZ_ASSERT(NS_IsMainThread());
4138 if (mDocGroup) {
4139 return mDocGroup->AbstractMainThreadFor(aCategory);
4140 }
4141 return DispatcherTrait::AbstractMainThreadFor(aCategory);
4142 }
4143
NoteScriptTrackingStatus(const nsACString & aURL,bool aIsTracking)4144 void Document::NoteScriptTrackingStatus(const nsACString& aURL,
4145 bool aIsTracking) {
4146 if (aIsTracking) {
4147 mTrackingScripts.Insert(aURL);
4148 } else {
4149 MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
4150 }
4151 }
4152
IsScriptTracking(JSContext * aCx) const4153 bool Document::IsScriptTracking(JSContext* aCx) const {
4154 JS::AutoFilename filename;
4155 uint32_t line = 0;
4156 uint32_t column = 0;
4157 if (!JS::DescribeScriptedCaller(aCx, &filename, &line, &column)) {
4158 return false;
4159 }
4160 return mTrackingScripts.Contains(nsDependentCString(filename.get()));
4161 }
4162
GetContentType(nsAString & aContentType)4163 void Document::GetContentType(nsAString& aContentType) {
4164 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
4165 }
4166
SetContentType(const nsACString & aContentType)4167 void Document::SetContentType(const nsACString& aContentType) {
4168 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
4169 aContentType.EqualsLiteral("application/xhtml+xml")) {
4170 mDefaultElementType = kNameSpaceID_XHTML;
4171 }
4172
4173 mCachedEncoder = nullptr;
4174 mContentType = aContentType;
4175 }
4176
GetAllowPlugins()4177 bool Document::GetAllowPlugins() {
4178 // First, we ask our docshell if it allows plugins.
4179 auto* browsingContext = GetBrowsingContext();
4180
4181 if (browsingContext) {
4182 if (!browsingContext->GetAllowPlugins()) {
4183 return false;
4184 }
4185
4186 // If the docshell allows plugins, we check whether
4187 // we are sandboxed and plugins should not be allowed.
4188 if (mSandboxFlags & SANDBOXED_PLUGINS) {
4189 return false;
4190 }
4191 }
4192
4193 FlashClassification classification = DocumentFlashClassification();
4194 if (classification == FlashClassification::Denied) {
4195 return false;
4196 }
4197
4198 return true;
4199 }
4200
HasPendingInitialTranslation()4201 bool Document::HasPendingInitialTranslation() {
4202 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready;
4203 }
4204
GetL10n()4205 DocumentL10n* Document::GetL10n() { return mDocumentL10n; }
4206
DocumentSupportsL10n(JSContext * aCx,JSObject * aObject)4207 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
4208 JS::Rooted<JSObject*> object(aCx, aObject);
4209 nsCOMPtr<nsIPrincipal> callerPrincipal =
4210 nsContentUtils::SubjectPrincipal(aCx);
4211 nsGlobalWindowInner* win = xpc::WindowOrNull(object);
4212 bool allowed = false;
4213 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr,
4214 &allowed);
4215 return allowed;
4216 }
4217
LocalizationLinkAdded(Element * aLinkElement)4218 void Document::LocalizationLinkAdded(Element* aLinkElement) {
4219 if (!AllowsL10n()) {
4220 return;
4221 }
4222
4223 nsAutoString href;
4224 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
4225
4226 if (!mDocumentL10n) {
4227 Element* elem = GetDocumentElement();
4228 MOZ_DIAGNOSTIC_ASSERT(elem);
4229
4230 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync);
4231 mDocumentL10n = DocumentL10n::Create(this, isSync);
4232 if (NS_WARN_IF(!mDocumentL10n)) {
4233 return;
4234 }
4235 }
4236
4237 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href));
4238
4239 if (mReadyState >= READYSTATE_INTERACTIVE) {
4240 nsContentUtils::AddScriptRunner(NewRunnableMethod(
4241 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n,
4242 &DocumentL10n::TriggerInitialTranslation));
4243 } else {
4244 if (!mDocumentL10n->mBlockingLayout) {
4245 // Our initial translation is going to block layout start. Make sure
4246 // we don't fire the load event until after that stops happening and
4247 // layout has a chance to start.
4248 BlockOnload();
4249 mDocumentL10n->mBlockingLayout = true;
4250 }
4251 }
4252 }
4253
LocalizationLinkRemoved(Element * aLinkElement)4254 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
4255 if (!AllowsL10n()) {
4256 return;
4257 }
4258
4259 if (mDocumentL10n) {
4260 nsAutoString href;
4261 aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
4262 uint32_t remaining =
4263 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href));
4264 if (remaining == 0) {
4265 if (mDocumentL10n->mBlockingLayout) {
4266 mDocumentL10n->mBlockingLayout = false;
4267 UnblockOnload(/* aFireSync = */ false);
4268 }
4269 mDocumentL10n = nullptr;
4270 }
4271 }
4272 }
4273
4274 /**
4275 * This method should be called once the end of the l10n
4276 * resource container has been parsed.
4277 *
4278 * In XUL this is the end of the first </linkset>,
4279 * In XHTML/HTML this is the end of </head>.
4280 *
4281 * This milestone is used to allow for batch
4282 * localization context I/O and building done
4283 * once when all resources in the document have been
4284 * collected.
4285 */
OnL10nResourceContainerParsed()4286 void Document::OnL10nResourceContainerParsed() {
4287 // XXX: This is a scaffolding for where we might inject prefetch
4288 // in bug 1717241.
4289 }
4290
OnParsingCompleted()4291 void Document::OnParsingCompleted() {
4292 // Let's call it again, in case the resource
4293 // container has not been closed, and only
4294 // now we're closing the document.
4295 OnL10nResourceContainerParsed();
4296
4297 if (mDocumentL10n) {
4298 RefPtr<DocumentL10n> l10n = mDocumentL10n;
4299 l10n->TriggerInitialTranslation();
4300 }
4301 }
4302
InitialTranslationCompleted(bool aL10nCached)4303 void Document::InitialTranslationCompleted(bool aL10nCached) {
4304 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) {
4305 // This means we blocked the load event in LocalizationLinkAdded. It's
4306 // important that the load blocker removal here be async, because our caller
4307 // will notify the content sink after us, and we want the content sync's
4308 // work to happen before the load event fires.
4309 mDocumentL10n->mBlockingLayout = false;
4310 UnblockOnload(/* aFireSync = */ false);
4311 }
4312
4313 mL10nProtoElements.Clear();
4314
4315 nsXULPrototypeDocument* proto = GetPrototype();
4316 if (proto) {
4317 proto->SetIsL10nCached(aL10nCached);
4318 }
4319 }
4320
AllowsL10n() const4321 bool Document::AllowsL10n() const {
4322 if (IsStaticDocument()) {
4323 // We don't allow l10n on static documents, because the nodes are already
4324 // cloned translated, and static docs don't get parsed so we never
4325 // TriggerInitialTranslation, etc, so a load blocker would keep hanging
4326 // forever.
4327 return false;
4328 }
4329 bool allowed = false;
4330 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed);
4331 return allowed;
4332 }
4333
IsWebAnimationsEnabled(JSContext * aCx,JSObject *)4334 bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
4335 MOZ_ASSERT(NS_IsMainThread());
4336
4337 return nsContentUtils::IsSystemCaller(aCx) ||
4338 StaticPrefs::dom_animations_api_core_enabled();
4339 }
4340
IsWebAnimationsEnabled(CallerType aCallerType)4341 bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
4342 MOZ_ASSERT(NS_IsMainThread());
4343
4344 return aCallerType == dom::CallerType::System ||
4345 StaticPrefs::dom_animations_api_core_enabled();
4346 }
4347
IsWebAnimationsGetAnimationsEnabled(JSContext * aCx,JSObject *)4348 bool Document::IsWebAnimationsGetAnimationsEnabled(JSContext* aCx,
4349 JSObject* /*unused*/
4350 ) {
4351 MOZ_ASSERT(NS_IsMainThread());
4352
4353 return nsContentUtils::IsSystemCaller(aCx) ||
4354 StaticPrefs::dom_animations_api_getAnimations_enabled();
4355 }
4356
AreWebAnimationsImplicitKeyframesEnabled(JSContext * aCx,JSObject *)4357 bool Document::AreWebAnimationsImplicitKeyframesEnabled(JSContext* aCx,
4358 JSObject* /*unused*/
4359 ) {
4360 MOZ_ASSERT(NS_IsMainThread());
4361
4362 return nsContentUtils::IsSystemCaller(aCx) ||
4363 StaticPrefs::dom_animations_api_implicit_keyframes_enabled();
4364 }
4365
AreWebAnimationsTimelinesEnabled(JSContext * aCx,JSObject *)4366 bool Document::AreWebAnimationsTimelinesEnabled(JSContext* aCx,
4367 JSObject* /*unused*/
4368 ) {
4369 MOZ_ASSERT(NS_IsMainThread());
4370
4371 return nsContentUtils::IsSystemCaller(aCx) ||
4372 StaticPrefs::dom_animations_api_timelines_enabled();
4373 }
4374
Timeline()4375 DocumentTimeline* Document::Timeline() {
4376 if (!mDocumentTimeline) {
4377 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
4378 }
4379
4380 return mDocumentTimeline;
4381 }
4382
GetSVGRootElement() const4383 SVGSVGElement* Document::GetSVGRootElement() const {
4384 Element* root = GetRootElement();
4385 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) {
4386 return nullptr;
4387 }
4388 return static_cast<SVGSVGElement*>(root);
4389 }
4390
4391 /* Return true if the document is in the focused top-level window, and is an
4392 * ancestor of the focused DOMWindow. */
HasFocus(ErrorResult & rv) const4393 bool Document::HasFocus(ErrorResult& rv) const {
4394 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4395 if (!fm) {
4396 rv.Throw(NS_ERROR_NOT_AVAILABLE);
4397 return false;
4398 }
4399
4400 BrowsingContext* bc = GetBrowsingContext();
4401 if (!bc) {
4402 return false;
4403 }
4404
4405 if (!fm->IsInActiveWindow(bc)) {
4406 return false;
4407 }
4408
4409 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext());
4410 }
4411
GetDesignMode(nsAString & aDesignMode)4412 void Document::GetDesignMode(nsAString& aDesignMode) {
4413 if (IsInDesignMode()) {
4414 aDesignMode.AssignLiteral("on");
4415 } else {
4416 aDesignMode.AssignLiteral("off");
4417 }
4418 }
4419
SetDesignMode(const nsAString & aDesignMode,nsIPrincipal & aSubjectPrincipal,ErrorResult & rv)4420 void Document::SetDesignMode(const nsAString& aDesignMode,
4421 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) {
4422 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
4423 }
4424
NotifyEditableStateChange(Document & aDoc)4425 static void NotifyEditableStateChange(Document& aDoc) {
4426 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4427 nsMutationGuard g;
4428 #endif
4429 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node;
4430 node = node->GetNextNode(&aDoc)) {
4431 if (auto* element = Element::FromNode(node)) {
4432 element->UpdateState(true);
4433 }
4434 }
4435 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0));
4436 }
4437
SetDesignMode(const nsAString & aDesignMode,const Maybe<nsIPrincipal * > & aSubjectPrincipal,ErrorResult & rv)4438 void Document::SetDesignMode(const nsAString& aDesignMode,
4439 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
4440 ErrorResult& rv) {
4441 if (aSubjectPrincipal.isSome() &&
4442 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
4443 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
4444 return;
4445 }
4446 const bool editableMode = IsInDesignMode();
4447 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
4448 SetEditableFlag(!editableMode);
4449 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic
4450 // state of all descendant elements of it. Update that now.
4451 NotifyEditableStateChange(*this);
4452 rv = EditingStateChanged();
4453 }
4454 }
4455
GetMidasCommandManager()4456 nsCommandManager* Document::GetMidasCommandManager() {
4457 // check if we have it cached
4458 if (mMidasCommandManager) {
4459 return mMidasCommandManager;
4460 }
4461
4462 nsPIDOMWindowOuter* window = GetWindow();
4463 if (!window) {
4464 return nullptr;
4465 }
4466
4467 nsIDocShell* docshell = window->GetDocShell();
4468 if (!docshell) {
4469 return nullptr;
4470 }
4471
4472 mMidasCommandManager = docshell->GetCommandManager();
4473 return mMidasCommandManager;
4474 }
4475
4476 // static
EnsureInitializeInternalCommandDataHashtable()4477 void Document::EnsureInitializeInternalCommandDataHashtable() {
4478 if (sInternalCommandDataHashtable) {
4479 return;
4480 }
4481 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
4482 sInternalCommandDataHashtable = new InternalCommandDataHashtable();
4483 // clang-format off
4484 sInternalCommandDataHashtable->InsertOrUpdate(
4485 u"bold"_ns,
4486 InternalCommandData(
4487 "cmd_bold",
4488 Command::FormatBold,
4489 ExecCommandParam::Ignore,
4490 StyleUpdatingCommand::GetInstance,
4491 CommandOnTextEditor::Disabled));
4492 sInternalCommandDataHashtable->InsertOrUpdate(
4493 u"italic"_ns,
4494 InternalCommandData(
4495 "cmd_italic",
4496 Command::FormatItalic,
4497 ExecCommandParam::Ignore,
4498 StyleUpdatingCommand::GetInstance,
4499 CommandOnTextEditor::Disabled));
4500 sInternalCommandDataHashtable->InsertOrUpdate(
4501 u"underline"_ns,
4502 InternalCommandData(
4503 "cmd_underline",
4504 Command::FormatUnderline,
4505 ExecCommandParam::Ignore,
4506 StyleUpdatingCommand::GetInstance,
4507 CommandOnTextEditor::Disabled));
4508 sInternalCommandDataHashtable->InsertOrUpdate(
4509 u"strikethrough"_ns,
4510 InternalCommandData(
4511 "cmd_strikethrough",
4512 Command::FormatStrikeThrough,
4513 ExecCommandParam::Ignore,
4514 StyleUpdatingCommand::GetInstance,
4515 CommandOnTextEditor::Disabled));
4516 sInternalCommandDataHashtable->InsertOrUpdate(
4517 u"subscript"_ns,
4518 InternalCommandData(
4519 "cmd_subscript",
4520 Command::FormatSubscript,
4521 ExecCommandParam::Ignore,
4522 StyleUpdatingCommand::GetInstance,
4523 CommandOnTextEditor::Disabled));
4524 sInternalCommandDataHashtable->InsertOrUpdate(
4525 u"superscript"_ns,
4526 InternalCommandData(
4527 "cmd_superscript",
4528 Command::FormatSuperscript,
4529 ExecCommandParam::Ignore,
4530 StyleUpdatingCommand::GetInstance,
4531 CommandOnTextEditor::Disabled));
4532 sInternalCommandDataHashtable->InsertOrUpdate(
4533 u"cut"_ns,
4534 InternalCommandData(
4535 "cmd_cut",
4536 Command::Cut,
4537 ExecCommandParam::Ignore,
4538 CutCommand::GetInstance,
4539 CommandOnTextEditor::Enabled));
4540 sInternalCommandDataHashtable->InsertOrUpdate(
4541 u"copy"_ns,
4542 InternalCommandData(
4543 "cmd_copy",
4544 Command::Copy,
4545 ExecCommandParam::Ignore,
4546 CopyCommand::GetInstance,
4547 CommandOnTextEditor::Enabled));
4548 sInternalCommandDataHashtable->InsertOrUpdate(
4549 u"paste"_ns,
4550 InternalCommandData(
4551 "cmd_paste",
4552 Command::Paste,
4553 ExecCommandParam::Ignore,
4554 PasteCommand::GetInstance,
4555 CommandOnTextEditor::Enabled));
4556 sInternalCommandDataHashtable->InsertOrUpdate(
4557 u"delete"_ns,
4558 InternalCommandData(
4559 "cmd_deleteCharBackward",
4560 Command::DeleteCharBackward,
4561 ExecCommandParam::Ignore,
4562 DeleteCommand::GetInstance,
4563 CommandOnTextEditor::Enabled));
4564 sInternalCommandDataHashtable->InsertOrUpdate(
4565 u"forwarddelete"_ns,
4566 InternalCommandData(
4567 "cmd_deleteCharForward",
4568 Command::DeleteCharForward,
4569 ExecCommandParam::Ignore,
4570 DeleteCommand::GetInstance,
4571 CommandOnTextEditor::Enabled));
4572 sInternalCommandDataHashtable->InsertOrUpdate(
4573 u"selectall"_ns,
4574 InternalCommandData(
4575 "cmd_selectAll",
4576 Command::SelectAll,
4577 ExecCommandParam::Ignore,
4578 SelectAllCommand::GetInstance,
4579 CommandOnTextEditor::Enabled));
4580 sInternalCommandDataHashtable->InsertOrUpdate(
4581 u"undo"_ns,
4582 InternalCommandData(
4583 "cmd_undo",
4584 Command::HistoryUndo,
4585 ExecCommandParam::Ignore,
4586 UndoCommand::GetInstance,
4587 CommandOnTextEditor::Enabled));
4588 sInternalCommandDataHashtable->InsertOrUpdate(
4589 u"redo"_ns,
4590 InternalCommandData(
4591 "cmd_redo",
4592 Command::HistoryRedo,
4593 ExecCommandParam::Ignore,
4594 RedoCommand::GetInstance,
4595 CommandOnTextEditor::Enabled));
4596 sInternalCommandDataHashtable->InsertOrUpdate(
4597 u"indent"_ns,
4598 InternalCommandData("cmd_indent",
4599 Command::FormatIndent,
4600 ExecCommandParam::Ignore,
4601 IndentCommand::GetInstance,
4602 CommandOnTextEditor::Disabled));
4603 sInternalCommandDataHashtable->InsertOrUpdate(
4604 u"outdent"_ns,
4605 InternalCommandData(
4606 "cmd_outdent",
4607 Command::FormatOutdent,
4608 ExecCommandParam::Ignore,
4609 OutdentCommand::GetInstance,
4610 CommandOnTextEditor::Disabled));
4611 sInternalCommandDataHashtable->InsertOrUpdate(
4612 u"backcolor"_ns,
4613 InternalCommandData(
4614 "cmd_highlight",
4615 Command::FormatBackColor,
4616 ExecCommandParam::String,
4617 HighlightColorStateCommand::GetInstance,
4618 CommandOnTextEditor::Disabled));
4619 sInternalCommandDataHashtable->InsertOrUpdate(
4620 u"hilitecolor"_ns,
4621 InternalCommandData(
4622 "cmd_highlight",
4623 Command::FormatBackColor,
4624 ExecCommandParam::String,
4625 HighlightColorStateCommand::GetInstance,
4626 CommandOnTextEditor::Disabled));
4627 sInternalCommandDataHashtable->InsertOrUpdate(
4628 u"forecolor"_ns,
4629 InternalCommandData(
4630 "cmd_fontColor",
4631 Command::FormatFontColor,
4632 ExecCommandParam::String,
4633 FontColorStateCommand::GetInstance,
4634 CommandOnTextEditor::Disabled));
4635 sInternalCommandDataHashtable->InsertOrUpdate(
4636 u"fontname"_ns,
4637 InternalCommandData(
4638 "cmd_fontFace",
4639 Command::FormatFontName,
4640 ExecCommandParam::String,
4641 FontFaceStateCommand::GetInstance,
4642 CommandOnTextEditor::Disabled));
4643 sInternalCommandDataHashtable->InsertOrUpdate(
4644 u"fontsize"_ns,
4645 InternalCommandData(
4646 "cmd_fontSize",
4647 Command::FormatFontSize,
4648 ExecCommandParam::String,
4649 FontSizeStateCommand::GetInstance,
4650 CommandOnTextEditor::Disabled));
4651 sInternalCommandDataHashtable->InsertOrUpdate(
4652 u"increasefontsize"_ns,
4653 InternalCommandData(
4654 "cmd_increaseFont",
4655 Command::FormatIncreaseFontSize,
4656 ExecCommandParam::Ignore,
4657 IncreaseFontSizeCommand::GetInstance,
4658 CommandOnTextEditor::Disabled));
4659 sInternalCommandDataHashtable->InsertOrUpdate(
4660 u"decreasefontsize"_ns,
4661 InternalCommandData(
4662 "cmd_decreaseFont",
4663 Command::FormatDecreaseFontSize,
4664 ExecCommandParam::Ignore,
4665 DecreaseFontSizeCommand::GetInstance,
4666 CommandOnTextEditor::Disabled));
4667 sInternalCommandDataHashtable->InsertOrUpdate(
4668 u"inserthorizontalrule"_ns,
4669 InternalCommandData(
4670 "cmd_insertHR",
4671 Command::InsertHorizontalRule,
4672 ExecCommandParam::Ignore,
4673 InsertTagCommand::GetInstance,
4674 CommandOnTextEditor::Disabled));
4675 sInternalCommandDataHashtable->InsertOrUpdate(
4676 u"createlink"_ns,
4677 InternalCommandData(
4678 "cmd_insertLinkNoUI",
4679 Command::InsertLink,
4680 ExecCommandParam::String,
4681 InsertTagCommand::GetInstance,
4682 CommandOnTextEditor::Disabled));
4683 sInternalCommandDataHashtable->InsertOrUpdate(
4684 u"insertimage"_ns,
4685 InternalCommandData(
4686 "cmd_insertImageNoUI",
4687 Command::InsertImage,
4688 ExecCommandParam::String,
4689 InsertTagCommand::GetInstance,
4690 CommandOnTextEditor::Disabled));
4691 sInternalCommandDataHashtable->InsertOrUpdate(
4692 u"inserthtml"_ns,
4693 InternalCommandData(
4694 "cmd_insertHTML",
4695 Command::InsertHTML,
4696 ExecCommandParam::String,
4697 InsertHTMLCommand::GetInstance,
4698 // TODO: Chromium inserts text content of the document fragment
4699 // created from the param.
4700 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8
4701 CommandOnTextEditor::Disabled));
4702 sInternalCommandDataHashtable->InsertOrUpdate(
4703 u"inserttext"_ns,
4704 InternalCommandData(
4705 "cmd_insertText",
4706 Command::InsertText,
4707 ExecCommandParam::String,
4708 InsertPlaintextCommand::GetInstance,
4709 CommandOnTextEditor::Enabled));
4710 sInternalCommandDataHashtable->InsertOrUpdate(
4711 u"gethtml"_ns,
4712 InternalCommandData(
4713 "cmd_getContents",
4714 Command::GetHTML,
4715 ExecCommandParam::Ignore,
4716 nullptr, // Not defined in EditorCommands.h
4717 // getHTML command is not supported by Chromium, and we return HTML
4718 // source code at selected range. So, let's return selected text
4719 // when `<input>` or `<textarea>` has focus.
4720 CommandOnTextEditor::Enabled));
4721 sInternalCommandDataHashtable->InsertOrUpdate(
4722 u"justifyleft"_ns,
4723 InternalCommandData(
4724 "cmd_align",
4725 Command::FormatJustifyLeft,
4726 ExecCommandParam::Ignore, // Will be set to "left"
4727 AlignCommand::GetInstance,
4728 CommandOnTextEditor::Disabled));
4729 sInternalCommandDataHashtable->InsertOrUpdate(
4730 u"justifyright"_ns,
4731 InternalCommandData(
4732 "cmd_align",
4733 Command::FormatJustifyRight,
4734 ExecCommandParam::Ignore, // Will be set to "right"
4735 AlignCommand::GetInstance,
4736 CommandOnTextEditor::Disabled));
4737 sInternalCommandDataHashtable->InsertOrUpdate(
4738 u"justifycenter"_ns,
4739 InternalCommandData(
4740 "cmd_align",
4741 Command::FormatJustifyCenter,
4742 ExecCommandParam::Ignore, // Will be set to "center"
4743 AlignCommand::GetInstance,
4744 CommandOnTextEditor::Disabled));
4745 sInternalCommandDataHashtable->InsertOrUpdate(
4746 u"justifyfull"_ns,
4747 InternalCommandData(
4748 "cmd_align",
4749 Command::FormatJustifyFull,
4750 ExecCommandParam::Ignore, // Will be set to "justify"
4751 AlignCommand::GetInstance,
4752 CommandOnTextEditor::Disabled));
4753 sInternalCommandDataHashtable->InsertOrUpdate(
4754 u"removeformat"_ns,
4755 InternalCommandData(
4756 "cmd_removeStyles",
4757 Command::FormatRemove,
4758 ExecCommandParam::Ignore,
4759 RemoveStylesCommand::GetInstance,
4760 CommandOnTextEditor::Disabled));
4761 sInternalCommandDataHashtable->InsertOrUpdate(
4762 u"unlink"_ns,
4763 InternalCommandData(
4764 "cmd_removeLinks",
4765 Command::FormatRemoveLink,
4766 ExecCommandParam::Ignore,
4767 StyleUpdatingCommand::GetInstance,
4768 CommandOnTextEditor::Disabled));
4769 sInternalCommandDataHashtable->InsertOrUpdate(
4770 u"insertorderedlist"_ns,
4771 InternalCommandData(
4772 "cmd_ol",
4773 Command::InsertOrderedList,
4774 ExecCommandParam::Ignore,
4775 ListCommand::GetInstance,
4776 CommandOnTextEditor::Disabled));
4777 sInternalCommandDataHashtable->InsertOrUpdate(
4778 u"insertunorderedlist"_ns,
4779 InternalCommandData(
4780 "cmd_ul",
4781 Command::InsertUnorderedList,
4782 ExecCommandParam::Ignore,
4783 ListCommand::GetInstance,
4784 CommandOnTextEditor::Disabled));
4785 sInternalCommandDataHashtable->InsertOrUpdate(
4786 u"insertparagraph"_ns,
4787 InternalCommandData(
4788 "cmd_insertParagraph",
4789 Command::InsertParagraph,
4790 ExecCommandParam::Ignore,
4791 InsertParagraphCommand::GetInstance,
4792 CommandOnTextEditor::Enabled));
4793 sInternalCommandDataHashtable->InsertOrUpdate(
4794 u"insertlinebreak"_ns,
4795 InternalCommandData(
4796 "cmd_insertLineBreak",
4797 Command::InsertLineBreak,
4798 ExecCommandParam::Ignore,
4799 InsertLineBreakCommand::GetInstance,
4800 CommandOnTextEditor::Enabled));
4801 sInternalCommandDataHashtable->InsertOrUpdate(
4802 u"formatblock"_ns,
4803 InternalCommandData(
4804 "cmd_paragraphState",
4805 Command::FormatBlock,
4806 ExecCommandParam::String,
4807 ParagraphStateCommand::GetInstance,
4808 CommandOnTextEditor::Disabled));
4809 sInternalCommandDataHashtable->InsertOrUpdate(
4810 u"heading"_ns,
4811 InternalCommandData(
4812 "cmd_paragraphState",
4813 Command::FormatBlock,
4814 ExecCommandParam::String,
4815 ParagraphStateCommand::GetInstance,
4816 CommandOnTextEditor::Disabled));
4817 sInternalCommandDataHashtable->InsertOrUpdate(
4818 u"styleWithCSS"_ns,
4819 InternalCommandData(
4820 "cmd_setDocumentUseCSS",
4821 Command::SetDocumentUseCSS,
4822 ExecCommandParam::Boolean,
4823 SetDocumentStateCommand::GetInstance,
4824 CommandOnTextEditor::FallThrough));
4825 sInternalCommandDataHashtable->InsertOrUpdate(
4826 u"usecss"_ns, // Legacy command
4827 InternalCommandData(
4828 "cmd_setDocumentUseCSS",
4829 Command::SetDocumentUseCSS,
4830 ExecCommandParam::InvertedBoolean,
4831 SetDocumentStateCommand::GetInstance,
4832 CommandOnTextEditor::FallThrough));
4833 sInternalCommandDataHashtable->InsertOrUpdate(
4834 u"contentReadOnly"_ns,
4835 InternalCommandData(
4836 "cmd_setDocumentReadOnly",
4837 Command::SetDocumentReadOnly,
4838 ExecCommandParam::Boolean,
4839 SetDocumentStateCommand::GetInstance,
4840 CommandOnTextEditor::Enabled));
4841 sInternalCommandDataHashtable->InsertOrUpdate(
4842 u"readonly"_ns, // Legacy command
4843 InternalCommandData(
4844 "cmd_setDocumentReadOnly",
4845 Command::SetDocumentReadOnly,
4846 ExecCommandParam::InvertedBoolean,
4847 SetDocumentStateCommand::GetInstance,
4848 CommandOnTextEditor::Enabled));
4849 sInternalCommandDataHashtable->InsertOrUpdate(
4850 u"insertBrOnReturn"_ns,
4851 InternalCommandData(
4852 "cmd_insertBrOnReturn",
4853 Command::SetDocumentInsertBROnEnterKeyPress,
4854 ExecCommandParam::Boolean,
4855 SetDocumentStateCommand::GetInstance,
4856 CommandOnTextEditor::FallThrough));
4857 sInternalCommandDataHashtable->InsertOrUpdate(
4858 u"defaultParagraphSeparator"_ns,
4859 InternalCommandData(
4860 "cmd_defaultParagraphSeparator",
4861 Command::SetDocumentDefaultParagraphSeparator,
4862 ExecCommandParam::String,
4863 SetDocumentStateCommand::GetInstance,
4864 CommandOnTextEditor::FallThrough));
4865 sInternalCommandDataHashtable->InsertOrUpdate(
4866 u"enableObjectResizing"_ns,
4867 InternalCommandData(
4868 "cmd_enableObjectResizing",
4869 Command::ToggleObjectResizers,
4870 ExecCommandParam::Boolean,
4871 SetDocumentStateCommand::GetInstance,
4872 CommandOnTextEditor::FallThrough));
4873 sInternalCommandDataHashtable->InsertOrUpdate(
4874 u"enableInlineTableEditing"_ns,
4875 InternalCommandData(
4876 "cmd_enableInlineTableEditing",
4877 Command::ToggleInlineTableEditor,
4878 ExecCommandParam::Boolean,
4879 SetDocumentStateCommand::GetInstance,
4880 CommandOnTextEditor::FallThrough));
4881 sInternalCommandDataHashtable->InsertOrUpdate(
4882 u"enableAbsolutePositionEditing"_ns,
4883 InternalCommandData(
4884 "cmd_enableAbsolutePositionEditing",
4885 Command::ToggleAbsolutePositionEditor,
4886 ExecCommandParam::Boolean,
4887 SetDocumentStateCommand::GetInstance,
4888 CommandOnTextEditor::FallThrough));
4889 #if 0
4890 // with empty string
4891 sInternalCommandDataHashtable->InsertOrUpdate(
4892 u"justifynone"_ns,
4893 InternalCommandData(
4894 "cmd_align",
4895 Command::Undefined,
4896 ExecCommandParam::Ignore,
4897 nullptr,
4898 CommandOnTextEditor::Disabled)); // Not implemented yet.
4899 // REQUIRED SPECIAL REVIEW special review
4900 sInternalCommandDataHashtable->InsertOrUpdate(
4901 u"saveas"_ns,
4902 InternalCommandData(
4903 "cmd_saveAs",
4904 Command::Undefined,
4905 ExecCommandParam::Boolean,
4906 nullptr,
4907 CommandOnTextEditor::FallThrough)); // Not implemented yet.
4908 // REQUIRED SPECIAL REVIEW special review
4909 sInternalCommandDataHashtable->InsertOrUpdate(
4910 u"print"_ns,
4911 InternalCommandData(
4912 "cmd_print",
4913 Command::Undefined,
4914 ExecCommandParam::Boolean,
4915 nullptr,
4916 CommandOnTextEditor::FallThrough)); // Not implemented yet.
4917 #endif // #if 0
4918 // clang-format on
4919 }
4920
ConvertToInternalCommand(const nsAString & aHTMLCommandName,const nsAString & aValue,nsAString * aAdjustedValue)4921 Document::InternalCommandData Document::ConvertToInternalCommand(
4922 const nsAString& aHTMLCommandName, const nsAString& aValue /* = u""_ns */,
4923 nsAString* aAdjustedValue /* = nullptr */) {
4924 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty());
4925 EnsureInitializeInternalCommandDataHashtable();
4926 InternalCommandData commandData;
4927 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) {
4928 return InternalCommandData();
4929 }
4930 // Ignore if the command is disabled by a corresponding pref due to Gecko
4931 // specific.
4932 switch (commandData.mCommand) {
4933 case Command::FormatIncreaseFontSize:
4934 MOZ_DIAGNOSTIC_ASSERT(
4935 aHTMLCommandName.LowerCaseEqualsLiteral("increasefontsize"));
4936 if (!StaticPrefs::dom_document_edit_command_increasefontsize_enabled()) {
4937 return InternalCommandData();
4938 }
4939 break;
4940 case Command::FormatDecreaseFontSize:
4941 MOZ_DIAGNOSTIC_ASSERT(
4942 aHTMLCommandName.LowerCaseEqualsLiteral("decreasefontsize"));
4943 if (!StaticPrefs::dom_document_edit_command_decreasefontsize_enabled()) {
4944 return InternalCommandData();
4945 }
4946 break;
4947 case Command::GetHTML:
4948 MOZ_DIAGNOSTIC_ASSERT(aHTMLCommandName.LowerCaseEqualsLiteral("gethtml"));
4949 if (!StaticPrefs::dom_document_edit_command_gethtml_enabled()) {
4950 return InternalCommandData();
4951 }
4952 break;
4953 case Command::FormatBlock:
4954 if (!StaticPrefs::dom_document_edit_command_heading_enabled() &&
4955 aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
4956 return InternalCommandData();
4957 }
4958 break;
4959 case Command::SetDocumentReadOnly:
4960 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() &&
4961 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) {
4962 return InternalCommandData();
4963 }
4964 if (!StaticPrefs::dom_document_edit_command_readonly_enabled() &&
4965 aHTMLCommandName.LowerCaseEqualsLiteral("readonly")) {
4966 return InternalCommandData();
4967 }
4968 break;
4969 case Command::SetDocumentInsertBROnEnterKeyPress:
4970 MOZ_DIAGNOSTIC_ASSERT(
4971 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn"));
4972 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) {
4973 return InternalCommandData();
4974 }
4975 break;
4976 default:
4977 break;
4978 }
4979 if (!aAdjustedValue) {
4980 // No further work to do
4981 return commandData;
4982 }
4983 switch (commandData.mExecCommandParam) {
4984 case ExecCommandParam::Ignore:
4985 // Just have to copy it, no checking
4986 switch (commandData.mCommand) {
4987 case Command::FormatJustifyLeft:
4988 aAdjustedValue->AssignLiteral("left");
4989 break;
4990 case Command::FormatJustifyRight:
4991 aAdjustedValue->AssignLiteral("right");
4992 break;
4993 case Command::FormatJustifyCenter:
4994 aAdjustedValue->AssignLiteral("center");
4995 break;
4996 case Command::FormatJustifyFull:
4997 aAdjustedValue->AssignLiteral("justify");
4998 break;
4999 default:
5000 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) ==
5001 EditorCommandParamType::None);
5002 break;
5003 }
5004 return commandData;
5005
5006 case ExecCommandParam::Boolean:
5007 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5008 EditorCommandParamType::Bool));
5009 // If this is a boolean value and it's not explicitly false (e.g. no
5010 // value). We default to "true" (see bug 301490).
5011 if (!aValue.LowerCaseEqualsLiteral("false")) {
5012 aAdjustedValue->AssignLiteral("true");
5013 } else {
5014 aAdjustedValue->AssignLiteral("false");
5015 }
5016 return commandData;
5017
5018 case ExecCommandParam::InvertedBoolean:
5019 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) &
5020 EditorCommandParamType::Bool));
5021 // For old backwards commands we invert the check.
5022 if (aValue.LowerCaseEqualsLiteral("false")) {
5023 aAdjustedValue->AssignLiteral("true");
5024 } else {
5025 aAdjustedValue->AssignLiteral("false");
5026 }
5027 return commandData;
5028
5029 case ExecCommandParam::String:
5030 MOZ_ASSERT(!!(
5031 EditorCommand::GetParamType(commandData.mCommand) &
5032 (EditorCommandParamType::String | EditorCommandParamType::CString)));
5033 switch (commandData.mCommand) {
5034 case Command::FormatBlock: {
5035 const char16_t* start = aValue.BeginReading();
5036 const char16_t* end = aValue.EndReading();
5037 if (start != end && *start == '<' && *(end - 1) == '>') {
5038 ++start;
5039 --end;
5040 }
5041 // XXX Should we reorder this array with actual usage?
5042 static const nsStaticAtom* kFormattableBlockTags[] = {
5043 // clang-format off
5044 nsGkAtoms::address,
5045 nsGkAtoms::blockquote,
5046 nsGkAtoms::dd,
5047 nsGkAtoms::div,
5048 nsGkAtoms::dl,
5049 nsGkAtoms::dt,
5050 nsGkAtoms::h1,
5051 nsGkAtoms::h2,
5052 nsGkAtoms::h3,
5053 nsGkAtoms::h4,
5054 nsGkAtoms::h5,
5055 nsGkAtoms::h6,
5056 nsGkAtoms::p,
5057 nsGkAtoms::pre,
5058 // clang-format on
5059 };
5060 nsAutoString value(nsDependentSubstring(start, end));
5061 ToLowerCase(value);
5062 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value);
5063 for (const nsStaticAtom* kTag : kFormattableBlockTags) {
5064 if (valueAtom == kTag) {
5065 kTag->ToString(*aAdjustedValue);
5066 return commandData;
5067 }
5068 }
5069 return InternalCommandData();
5070 }
5071 case Command::FormatFontSize: {
5072 // Per editing spec as of April 23, 2012, we need to reject the value
5073 // if it's not a valid floating-point number surrounded by optional
5074 // whitespace. Otherwise, we parse it as a legacy font size. For
5075 // now, we just parse as a legacy font size regardless (matching
5076 // WebKit) -- bug 747879.
5077 int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
5078 if (!size) {
5079 return InternalCommandData();
5080 }
5081 MOZ_ASSERT(aAdjustedValue->IsEmpty());
5082 aAdjustedValue->AppendInt(size);
5083 return commandData;
5084 }
5085 case Command::InsertImage:
5086 case Command::InsertLink:
5087 if (aValue.IsEmpty()) {
5088 // Invalid value, return false
5089 return InternalCommandData();
5090 }
5091 aAdjustedValue->Assign(aValue);
5092 return commandData;
5093 case Command::SetDocumentDefaultParagraphSeparator:
5094 if (!aValue.LowerCaseEqualsLiteral("div") &&
5095 !aValue.LowerCaseEqualsLiteral("p") &&
5096 !aValue.LowerCaseEqualsLiteral("br")) {
5097 // Invalid value
5098 return InternalCommandData();
5099 }
5100 aAdjustedValue->Assign(aValue);
5101 return commandData;
5102 default:
5103 aAdjustedValue->Assign(aValue);
5104 return commandData;
5105 }
5106
5107 default:
5108 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled");
5109 return InternalCommandData();
5110 }
5111 }
5112
AutoEditorCommandTarget(Document & aDocument,const InternalCommandData & aCommandData)5113 Document::AutoEditorCommandTarget::AutoEditorCommandTarget(
5114 Document& aDocument, const InternalCommandData& aCommandData)
5115 : mCommandData(aCommandData) {
5116 // We'll retrieve an editor with current DOM tree and layout information.
5117 // However, JS may have already hidden or remove exposed root content of
5118 // the editor. Therefore, we need the latest layout information here.
5119 aDocument.FlushPendingNotifications(FlushType::Layout);
5120 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) {
5121 mDoNothing = true;
5122 return;
5123 }
5124
5125 if (nsPresContext* presContext = aDocument.GetPresContext()) {
5126 // Consider context of command handling which is automatically resolved
5127 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5128 // The order is:
5129 // 1. TextEditor if there is an active element and it has TextEditor like
5130 // <input type="text"> or <textarea>.
5131 // 2. HTMLEditor for the document, if there is.
5132 // 3. Retarget to the DocShell or nsCommandManager as what we've done.
5133 if (aCommandData.IsCutOrCopyCommand()) {
5134 // Note that we used to use DocShell to handle `cut` and `copy` command
5135 // for dispatching corresponding events for making possible web apps to
5136 // implement their own editor without editable elements but supports
5137 // standard shortcut keys, etc. In this case, we prefer to use active
5138 // element's editor to keep same behavior.
5139 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5140 } else {
5141 mActiveEditor = nsContentUtils::GetActiveEditor(presContext);
5142 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext);
5143 if (!mActiveEditor) {
5144 mActiveEditor = mHTMLEditor;
5145 }
5146 }
5147 }
5148
5149 // Then, retrieve editor command class instance which should handle it
5150 // and can handle it now.
5151 if (!mActiveEditor) {
5152 // If the command is available without editor, we should redirect the
5153 // command to focused descendant with DocShell.
5154 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5155 mDoNothing = true;
5156 return;
5157 }
5158 return;
5159 }
5160
5161 // Otherwise, we should use EditorCommand instance (which is singleton
5162 // instance) when it's enabled.
5163 mEditorCommand = aCommandData.mGetEditorCommandFunc
5164 ? aCommandData.mGetEditorCommandFunc()
5165 : nullptr;
5166 if (!mEditorCommand) {
5167 mDoNothing = true;
5168 mActiveEditor = nullptr;
5169 mHTMLEditor = nullptr;
5170 return;
5171 }
5172
5173 if (IsCommandEnabled()) {
5174 return;
5175 }
5176
5177 // If the EditorCommand instance is disabled, we should do nothing if
5178 // the command requires an editor.
5179 if (aCommandData.IsAvailableOnlyWhenEditable()) {
5180 // Do nothing if editor specific commands is disabled (bug 760052).
5181 mDoNothing = true;
5182 return;
5183 }
5184
5185 // Otherwise, we should redirect it to focused descendant with DocShell.
5186 mEditorCommand = nullptr;
5187 mActiveEditor = nullptr;
5188 mHTMLEditor = nullptr;
5189 }
5190
GetTargetEditor() const5191 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const {
5192 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor;
5193 switch (mCommandData.mCommandOnTextEditor) {
5194 case CommandOnTextEditor::Enabled:
5195 return mActiveEditor;
5196 case CommandOnTextEditor::Disabled:
5197 return mActiveEditor && mActiveEditor->IsTextEditor()
5198 ? nullptr
5199 : mActiveEditor.get();
5200 case CommandOnTextEditor::FallThrough:
5201 return mHTMLEditor;
5202 }
5203 return nullptr;
5204 }
5205
IsEditable(Document * aDocument) const5206 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const {
5207 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) {
5208 // Make sure frames are up to date, since that can affect whether
5209 // we're editable.
5210 doc->FlushPendingNotifications(FlushType::Frames);
5211 }
5212 EditorBase* targetEditor = GetTargetEditor();
5213 if (targetEditor && targetEditor->IsTextEditor()) {
5214 // FYI: When `disabled` attribute is set, `TextEditor` treats it as
5215 // "readonly" too.
5216 return !targetEditor->IsReadonly();
5217 }
5218 return aDocument->IsEditingOn();
5219 }
5220
IsCommandEnabled() const5221 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const {
5222 EditorBase* targetEditor = GetTargetEditor();
5223 if (!targetEditor) {
5224 return false;
5225 }
5226 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5227 return MOZ_KnownLive(mEditorCommand)
5228 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor));
5229 }
5230
DoCommand(nsIPrincipal * aPrincipal) const5231 nsresult Document::AutoEditorCommandTarget::DoCommand(
5232 nsIPrincipal* aPrincipal) const {
5233 MOZ_ASSERT(!DoNothing());
5234 MOZ_ASSERT(mEditorCommand);
5235 EditorBase* targetEditor = GetTargetEditor();
5236 if (!targetEditor) {
5237 return NS_SUCCESS_DOM_NO_OPERATION;
5238 }
5239 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5240 return MOZ_KnownLive(mEditorCommand)
5241 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor),
5242 aPrincipal);
5243 }
5244
5245 template <typename ParamType>
DoCommandParam(const ParamType & aParam,nsIPrincipal * aPrincipal) const5246 nsresult Document::AutoEditorCommandTarget::DoCommandParam(
5247 const ParamType& aParam, nsIPrincipal* aPrincipal) const {
5248 MOZ_ASSERT(!DoNothing());
5249 MOZ_ASSERT(mEditorCommand);
5250 EditorBase* targetEditor = GetTargetEditor();
5251 if (!targetEditor) {
5252 return NS_SUCCESS_DOM_NO_OPERATION;
5253 }
5254 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5255 return MOZ_KnownLive(mEditorCommand)
5256 ->DoCommandParam(mCommandData.mCommand, aParam,
5257 MOZ_KnownLive(*targetEditor), aPrincipal);
5258 }
5259
GetCommandStateParams(nsCommandParams & aParams) const5260 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams(
5261 nsCommandParams& aParams) const {
5262 MOZ_ASSERT(mEditorCommand);
5263 EditorBase* targetEditor = GetTargetEditor();
5264 if (!targetEditor) {
5265 return NS_OK;
5266 }
5267 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor);
5268 return MOZ_KnownLive(mEditorCommand)
5269 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams),
5270 MOZ_KnownLive(targetEditor), nullptr);
5271 }
5272
ExecCommand(const nsAString & aHTMLCommandName,bool aShowUI,const nsAString & aValue,nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv)5273 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
5274 const nsAString& aValue,
5275 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
5276 // Only allow on HTML documents.
5277 if (!IsHTMLOrXHTML()) {
5278 aRv.ThrowInvalidStateError(
5279 "execCommand is only supported on HTML documents");
5280 return false;
5281 }
5282 // Otherwise, don't throw exception for compatibility with Chrome.
5283
5284 // if they are requesting UI from us, let's fail since we have no UI
5285 if (aShowUI) {
5286 return false;
5287 }
5288
5289 // If we're running an execCommand, we should just return false.
5290 // https://github.com/w3c/editing/issues/200#issuecomment-575241816
5291 if (!StaticPrefs::dom_document_exec_command_nested_calls_allowed() &&
5292 mIsRunningExecCommand) {
5293 return false;
5294 }
5295
5296 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
5297 // this might add some ugly JS dependencies?
5298
5299 nsAutoString adjustedValue;
5300 InternalCommandData commandData =
5301 ConvertToInternalCommand(aHTMLCommandName, aValue, &adjustedValue);
5302 switch (commandData.mCommand) {
5303 case Command::DoNothing:
5304 // "gethtml" command is a command to retrieve a string value, not executing
5305 // anything and not enough the `bool` value of `execCommand`. So, at here,
5306 // we do nothing for "gethtml" command.
5307 case Command::GetHTML:
5308 return false;
5309 case Command::FormatIncreaseFontSize:
5310 SetUseCounter(eUseCounter_custom_DocumentExecCommandIncreaseFontSize);
5311 break;
5312 case Command::FormatDecreaseFontSize:
5313 SetUseCounter(eUseCounter_custom_DocumentExecCommandDecreaseFontSize);
5314 break;
5315 case Command::FormatBlock:
5316 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5317 SetUseCounter(eUseCounter_custom_DocumentExecCommandHeading);
5318 }
5319 break;
5320 case Command::SetDocumentReadOnly:
5321 SetUseCounter(aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5322 ? eUseCounter_custom_DocumentExecCommandContentReadOnly
5323 : eUseCounter_custom_DocumentExecCommandReadOnly);
5324 break;
5325 default:
5326 break;
5327 }
5328
5329 // Do security check first.
5330 if (commandData.IsCutOrCopyCommand()) {
5331 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) {
5332 // We have rejected the event due to it not being performed in an
5333 // input-driven context therefore, we report the error to the console.
5334 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
5335 this, nsContentUtils::eDOM_PROPERTIES,
5336 "ExecCommandCutCopyDeniedNotInputDriven");
5337 return false;
5338 }
5339 } else if (commandData.IsPasteCommand()) {
5340 if (!nsContentUtils::PrincipalHasPermission(aSubjectPrincipal,
5341 nsGkAtoms::clipboardRead)) {
5342 return false;
5343 }
5344 }
5345
5346 AutoRunningExecCommandMarker markRunningExecCommand(*this);
5347
5348 // Next, consider context of command handling which is automatically resolved
5349 // by order of controllers in `nsCommandManager::GetControllerForCommand()`.
5350 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5351 if (commandData.IsAvailableOnlyWhenEditable() &&
5352 !editCommandTarget.IsEditable(this)) {
5353 return false;
5354 }
5355
5356 if (editCommandTarget.DoNothing()) {
5357 return false;
5358 }
5359
5360 // If we cannot use EditorCommand instance directly, we need to handle the
5361 // command with traditional path (i.e., with DocShell or nsCommandManager).
5362 if (!editCommandTarget.IsEditor()) {
5363 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable());
5364
5365 // Special case clipboard write commands like Command::Cut and
5366 // Command::Copy. For such commands, we need the behaviour from
5367 // nsWindowRoot::GetControllers() which is to look at the focused element,
5368 // and defer to a focused textbox's controller. The code past taken by
5369 // other commands in ExecCommand() always uses the window directly, rather
5370 // than deferring to the textbox, which is desireable for most editor
5371 // commands, but not these commands (as those should allow copying out of
5372 // embedded editors). This behaviour is invoked if we call DoCommand()
5373 // directly on the docShell.
5374 // XXX This means that we allow web app to pick up selected content in
5375 // descendant document and write it into the clipboard when a
5376 // descendant document has focus. However, Chromium does not allow
5377 // this and this seems that it's not good behavior from point of view
5378 // of security. We should treat this issue in another bug.
5379 if (commandData.IsCutOrCopyCommand()) {
5380 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
5381 if (!docShell) {
5382 return false;
5383 }
5384 nsresult rv = docShell->DoCommand(commandData.mXULCommandName);
5385 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
5386 return false;
5387 }
5388 return NS_SUCCEEDED(rv);
5389 }
5390
5391 // Otherwise (currently, only clipboard read commands like Command::Paste),
5392 // we don't need to redirect the command to focused subdocument.
5393 // Therefore, we should handle it with nsCommandManager as used to be.
5394 // It may dispatch only preceding event of editing on non-editable element
5395 // to make web apps possible to handle standard shortcut key, etc in
5396 // their own editor.
5397 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5398 if (!commandManager) {
5399 return false;
5400 }
5401
5402 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5403 if (!window) {
5404 return false;
5405 }
5406
5407 // Return false for disabled commands (bug 760052)
5408 if (!commandManager->IsCommandEnabled(
5409 nsDependentCString(commandData.mXULCommandName), window)) {
5410 return false;
5411 }
5412
5413 MOZ_ASSERT(commandData.IsPasteCommand() ||
5414 commandData.mCommand == Command::SelectAll);
5415 nsresult rv =
5416 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
5417 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5418 }
5419
5420 // Now, our target is fixed to the editor. So, we can use EditorCommand
5421 // in EditorCommandTarget directly.
5422
5423 EditorCommandParamType paramType =
5424 EditorCommand::GetParamType(commandData.mCommand);
5425
5426 // If we don't have meaningful parameter or the EditorCommand does not
5427 // require additional parameter, we can use `DoCommand()`.
5428 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) {
5429 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool));
5430 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal);
5431 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5432 }
5433
5434 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be
5435 // "true" or "false" here. So, we can use `DoCommandParam()` which takes
5436 // a `bool` value.
5437 if (!!(paramType & EditorCommandParamType::Bool)) {
5438 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") ||
5439 adjustedValue.EqualsLiteral("false"));
5440 nsresult rv = editCommandTarget.DoCommandParam(
5441 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal);
5442 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5443 }
5444
5445 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter
5446 // in this case. However, `paramType` may contain both `String` and
5447 // `CString` but in such case, we should use `DoCommandParam()` which
5448 // takes `nsAString`. So, we should check whether `paramType` contains
5449 // `String` or not first.
5450 if (!!(paramType & EditorCommandParamType::String)) {
5451 MOZ_ASSERT(!adjustedValue.IsVoid());
5452 nsresult rv =
5453 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal);
5454 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5455 }
5456
5457 // Finally, `paramType` should have `CString`. We should use
5458 // `DoCommandParam()` which takes `nsACString`.
5459 if (!!(paramType & EditorCommandParamType::CString)) {
5460 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue);
5461 MOZ_ASSERT(!utf8Value.IsVoid());
5462 nsresult rv =
5463 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal);
5464 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;
5465 }
5466
5467 MOZ_ASSERT_UNREACHABLE(
5468 "Not yet implemented to handle new EditorCommandParamType");
5469 return false;
5470 }
5471
QueryCommandEnabled(const nsAString & aHTMLCommandName,nsIPrincipal & aSubjectPrincipal,ErrorResult & aRv)5472 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName,
5473 nsIPrincipal& aSubjectPrincipal,
5474 ErrorResult& aRv) {
5475 // Only allow on HTML documents.
5476 if (!IsHTMLOrXHTML()) {
5477 aRv.ThrowInvalidStateError(
5478 "queryCommandEnabled is only supported on HTML documents");
5479 return false;
5480 }
5481 // Otherwise, don't throw exception for compatibility with Chrome.
5482
5483 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5484 switch (commandData.mCommand) {
5485 case Command::DoNothing:
5486 return false;
5487 case Command::FormatIncreaseFontSize:
5488 SetUseCounter(
5489 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledIncreaseFontSize);
5490 break;
5491 case Command::FormatDecreaseFontSize:
5492 SetUseCounter(
5493 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledDecreaseFontSize);
5494 break;
5495 case Command::GetHTML:
5496 SetUseCounter(
5497 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledGetHTML);
5498 break;
5499 case Command::FormatBlock:
5500 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5501 SetUseCounter(
5502 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledHeading);
5503 }
5504 break;
5505 case Command::SetDocumentReadOnly:
5506 SetUseCounter(
5507 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5508 ? eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly
5509 : eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledReadOnly);
5510 break;
5511 case Command::SetDocumentInsertBROnEnterKeyPress:
5512 SetUseCounter(
5513 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5514 break;
5515 default:
5516 break;
5517 }
5518
5519 // cut & copy are always allowed
5520 if (commandData.IsCutOrCopyCommand()) {
5521 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal);
5522 }
5523
5524 // Report false for restricted commands
5525 if (commandData.IsPasteCommand() && !aSubjectPrincipal.IsSystemPrincipal()) {
5526 return false;
5527 }
5528
5529 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5530 if (commandData.IsAvailableOnlyWhenEditable() &&
5531 !editCommandTarget.IsEditable(this)) {
5532 return false;
5533 }
5534
5535 if (editCommandTarget.IsEditor()) {
5536 return editCommandTarget.IsCommandEnabled();
5537 }
5538
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;
5543 }
5544
5545 nsPIDOMWindowOuter* window = GetWindow();
5546 if (!window) {
5547 return false;
5548 }
5549
5550 return commandManager->IsCommandEnabled(
5551 nsDependentCString(commandData.mXULCommandName), window);
5552 }
5553
QueryCommandIndeterm(const nsAString & aHTMLCommandName,ErrorResult & aRv)5554 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName,
5555 ErrorResult& aRv) {
5556 // Only allow on HTML documents.
5557 if (!IsHTMLOrXHTML()) {
5558 aRv.ThrowInvalidStateError(
5559 "queryCommandIndeterm is only supported on HTML documents");
5560 return false;
5561 }
5562 // Otherwise, don't throw exception for compatibility with Chrome.
5563
5564 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5565 if (commandData.mCommand == Command::DoNothing) {
5566 return false;
5567 }
5568
5569 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5570 if (commandData.IsAvailableOnlyWhenEditable() &&
5571 !editCommandTarget.IsEditable(this)) {
5572 return false;
5573 }
5574 RefPtr<nsCommandParams> params = new nsCommandParams();
5575 if (editCommandTarget.IsEditor()) {
5576 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5577 return false;
5578 }
5579 } else {
5580 // get command manager and dispatch command to our window if it's acceptable
5581 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5582 if (!commandManager) {
5583 return false;
5584 }
5585
5586 nsPIDOMWindowOuter* window = GetWindow();
5587 if (!window) {
5588 return false;
5589 }
5590
5591 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5592 window, params))) {
5593 return false;
5594 }
5595 }
5596
5597 // If command does not have a state_mixed value, this call fails and sets
5598 // retval to false. This is fine -- we want to return false in that case
5599 // anyway (bug 738385), so we just don't throw regardless.
5600 return params->GetBool("state_mixed");
5601 }
5602
QueryCommandState(const nsAString & aHTMLCommandName,ErrorResult & aRv)5603 bool Document::QueryCommandState(const nsAString& aHTMLCommandName,
5604 ErrorResult& aRv) {
5605 // Only allow on HTML documents.
5606 if (!IsHTMLOrXHTML()) {
5607 aRv.ThrowInvalidStateError(
5608 "queryCommandState is only supported on HTML documents");
5609 return false;
5610 }
5611 // Otherwise, don't throw exception for compatibility with Chrome.
5612
5613 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5614 switch (commandData.mCommand) {
5615 case Command::DoNothing:
5616 return false;
5617 case Command::GetHTML:
5618 SetUseCounter(eUseCounter_custom_DocumentQueryCommandStateOrValueGetHTML);
5619 break;
5620 case Command::FormatBlock:
5621 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5622 SetUseCounter(
5623 eUseCounter_custom_DocumentQueryCommandStateOrValueHeading);
5624 }
5625 break;
5626 case Command::SetDocumentReadOnly:
5627 SetUseCounter(
5628 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5629 ? eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly
5630 : eUseCounter_custom_DocumentQueryCommandStateOrValueReadOnly);
5631 break;
5632 case Command::SetDocumentInsertBROnEnterKeyPress:
5633 SetUseCounter(
5634 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5635 break;
5636 default:
5637 break;
5638 }
5639
5640 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) {
5641 // Per spec, state is supported for styleWithCSS but not useCSS, so we just
5642 // return false always.
5643 return false;
5644 }
5645
5646 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5647 if (commandData.IsAvailableOnlyWhenEditable() &&
5648 !editCommandTarget.IsEditable(this)) {
5649 return false;
5650 }
5651 RefPtr<nsCommandParams> params = new nsCommandParams();
5652 if (editCommandTarget.IsEditor()) {
5653 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5654 return false;
5655 }
5656 } else {
5657 // get command manager and dispatch command to our window if it's acceptable
5658 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5659 if (!commandManager) {
5660 return false;
5661 }
5662
5663 nsPIDOMWindowOuter* window = GetWindow();
5664 if (!window) {
5665 return false;
5666 }
5667
5668 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5669 window, params))) {
5670 return false;
5671 }
5672 }
5673
5674 // handle alignment as a special case (possibly other commands too?)
5675 // Alignment is special because the external api is individual
5676 // commands but internally we use cmd_align with different
5677 // parameters. When getting the state of this command, we need to
5678 // return the boolean for this particular alignment rather than the
5679 // string of 'which alignment is this?'
5680 switch (commandData.mCommand) {
5681 case Command::FormatJustifyLeft: {
5682 nsAutoCString currentValue;
5683 nsresult rv = params->GetCString("state_attribute", currentValue);
5684 if (NS_FAILED(rv)) {
5685 return false;
5686 }
5687 return currentValue.EqualsLiteral("left");
5688 }
5689 case Command::FormatJustifyRight: {
5690 nsAutoCString currentValue;
5691 nsresult rv = params->GetCString("state_attribute", currentValue);
5692 if (NS_FAILED(rv)) {
5693 return false;
5694 }
5695 return currentValue.EqualsLiteral("right");
5696 }
5697 case Command::FormatJustifyCenter: {
5698 nsAutoCString currentValue;
5699 nsresult rv = params->GetCString("state_attribute", currentValue);
5700 if (NS_FAILED(rv)) {
5701 return false;
5702 }
5703 return currentValue.EqualsLiteral("center");
5704 }
5705 case Command::FormatJustifyFull: {
5706 nsAutoCString currentValue;
5707 nsresult rv = params->GetCString("state_attribute", currentValue);
5708 if (NS_FAILED(rv)) {
5709 return false;
5710 }
5711 return currentValue.EqualsLiteral("justify");
5712 }
5713 default:
5714 break;
5715 }
5716
5717 // If command does not have a state_all value, this call fails and sets
5718 // retval to false. This is fine -- we want to return false in that case
5719 // anyway (bug 738385), so we just succeed and return false regardless.
5720 return params->GetBool("state_all");
5721 }
5722
QueryCommandSupported(const nsAString & aHTMLCommandName,CallerType aCallerType,ErrorResult & aRv)5723 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName,
5724 CallerType aCallerType, ErrorResult& aRv) {
5725 // Only allow on HTML documents.
5726 if (!IsHTMLOrXHTML()) {
5727 aRv.ThrowInvalidStateError(
5728 "queryCommandSupported is only supported on HTML documents");
5729 return false;
5730 }
5731 // Otherwise, don't throw exception for compatibility with Chrome.
5732
5733 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5734 switch (commandData.mCommand) {
5735 case Command::DoNothing:
5736 return false;
5737 case Command::FormatIncreaseFontSize:
5738 SetUseCounter(
5739 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledIncreaseFontSize);
5740 break;
5741 case Command::FormatDecreaseFontSize:
5742 SetUseCounter(
5743 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledDecreaseFontSize);
5744 break;
5745 case Command::GetHTML:
5746 SetUseCounter(
5747 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledGetHTML);
5748 break;
5749 case Command::FormatBlock:
5750 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5751 SetUseCounter(
5752 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledHeading);
5753 }
5754 break;
5755 case Command::SetDocumentReadOnly:
5756 SetUseCounter(
5757 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5758 ? eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly
5759 : eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledReadOnly);
5760 break;
5761 case Command::SetDocumentInsertBROnEnterKeyPress:
5762 SetUseCounter(
5763 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn);
5764 break;
5765 default:
5766 break;
5767 }
5768
5769 // Gecko technically supports all the clipboard commands including
5770 // cut/copy/paste, but non-privileged content will be unable to call
5771 // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
5772 // may also be disallowed to be called from non-privileged content.
5773 // For that reason, we report the support status of corresponding
5774 // command accordingly.
5775 if (aCallerType != CallerType::System) {
5776 if (commandData.IsPasteCommand()) {
5777 return false;
5778 }
5779 if (commandData.IsCutOrCopyCommand() &&
5780 !StaticPrefs::dom_allow_cut_copy()) {
5781 // XXXbz should we worry about correctly reporting "true" in the
5782 // "restricted, but we're an addon with clipboardWrite permissions" case?
5783 // See also nsContentUtils::IsCutCopyAllowed.
5784 return false;
5785 }
5786 }
5787
5788 // aHTMLCommandName is supported if it can be converted to a Midas command
5789 return true;
5790 }
5791
QueryCommandValue(const nsAString & aHTMLCommandName,nsAString & aValue,ErrorResult & aRv)5792 void Document::QueryCommandValue(const nsAString& aHTMLCommandName,
5793 nsAString& aValue, ErrorResult& aRv) {
5794 aValue.Truncate();
5795
5796 // Only allow on HTML documents.
5797 if (!IsHTMLOrXHTML()) {
5798 aRv.ThrowInvalidStateError(
5799 "queryCommandValue is only supported on HTML documents");
5800 return;
5801 }
5802 // Otherwise, don't throw exception for compatibility with Chrome.
5803
5804 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName);
5805 switch (commandData.mCommand) {
5806 case Command::DoNothing:
5807 // Return empty string
5808 return;
5809 case Command::GetHTML:
5810 SetUseCounter(eUseCounter_custom_DocumentQueryCommandStateOrValueGetHTML);
5811 break;
5812 case Command::FormatBlock:
5813 if (aHTMLCommandName.LowerCaseEqualsLiteral("heading")) {
5814 SetUseCounter(
5815 eUseCounter_custom_DocumentQueryCommandStateOrValueHeading);
5816 }
5817 break;
5818 case Command::SetDocumentReadOnly:
5819 SetUseCounter(
5820 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")
5821 ? eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly
5822 : eUseCounter_custom_DocumentQueryCommandStateOrValueReadOnly);
5823 break;
5824 case Command::SetDocumentInsertBROnEnterKeyPress:
5825 SetUseCounter(
5826 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn);
5827 break;
5828 default:
5829 break;
5830 }
5831
5832 AutoEditorCommandTarget editCommandTarget(*this, commandData);
5833 if (commandData.IsAvailableOnlyWhenEditable() &&
5834 !editCommandTarget.IsEditable(this)) {
5835 return;
5836 }
5837 RefPtr<nsCommandParams> params = new nsCommandParams();
5838 // FYI: Only GetHTML command is not implemented by editor. Use window's
5839 // command table instead.
5840 if (editCommandTarget.IsEditor()) {
5841 MOZ_ASSERT(commandData.mCommand != Command::GetHTML);
5842 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5843 return;
5844 }
5845
5846 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) {
5847 return;
5848 }
5849 } else {
5850 // get command manager and dispatch command to our window if it's acceptable
5851 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager();
5852 if (!commandManager) {
5853 return;
5854 }
5855
5856 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
5857 if (!window) {
5858 return;
5859 }
5860
5861 // this is a special command since we are calling DoCommand rather than
5862 // GetCommandState like the other commands
5863 if (commandData.mCommand == Command::GetHTML) {
5864 if (NS_FAILED(params->SetBool("selection_only", true))) {
5865 return;
5866 }
5867 if (NS_FAILED(params->SetCString("format", "text/html"_ns))) {
5868 return;
5869 }
5870 if (NS_FAILED(commandManager->DoCommand(commandData.mXULCommandName,
5871 params, window))) {
5872 return;
5873 }
5874 params->GetString("result", aValue);
5875 return;
5876 }
5877
5878 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) {
5879 return;
5880 }
5881
5882 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName,
5883 window, params))) {
5884 return;
5885 }
5886 }
5887
5888 // If command does not have a state_attribute value, this call fails, and
5889 // aValue will wind up being the empty string. This is fine -- we want to
5890 // return "" in that case anyway (bug 738385), so we just return NS_OK
5891 // regardless.
5892 nsAutoCString result;
5893 params->GetCString("state_attribute", result);
5894 CopyUTF8toUTF16(result, aValue);
5895 }
5896
MaybeEditingStateChanged()5897 void Document::MaybeEditingStateChanged() {
5898 if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
5899 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
5900 if (nsContentUtils::IsSafeToRunScript()) {
5901 EditingStateChanged();
5902 } else if (!mInDestructor) {
5903 nsContentUtils::AddScriptRunner(
5904 NewRunnableMethod("Document::MaybeEditingStateChanged", this,
5905 &Document::MaybeEditingStateChanged));
5906 }
5907 }
5908 }
5909
NotifyFetchOrXHRSuccess()5910 void Document::NotifyFetchOrXHRSuccess() {
5911 if (mShouldNotifyFetchSuccess) {
5912 nsContentUtils::DispatchEventOnlyToChrome(
5913 this, ToSupports(this), u"DOMDocFetchSuccess"_ns, CanBubble::eNo,
5914 Cancelable::eNo, /* DefaultAction */ nullptr);
5915 }
5916 }
5917
SetNotifyFetchSuccess(bool aShouldNotify)5918 void Document::SetNotifyFetchSuccess(bool aShouldNotify) {
5919 mShouldNotifyFetchSuccess = aShouldNotify;
5920 }
5921
SetNotifyFormOrPasswordRemoved(bool aShouldNotify)5922 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) {
5923 mShouldNotifyFormOrPasswordRemoved = aShouldNotify;
5924 }
5925
TearingDownEditor()5926 void Document::TearingDownEditor() {
5927 if (IsEditingOn()) {
5928 mEditingState = EditingState::eTearingDown;
5929 if (IsHTMLOrXHTML()) {
5930 RemoveContentEditableStyleSheets();
5931 }
5932 }
5933 }
5934
TurnEditingOff()5935 nsresult Document::TurnEditingOff() {
5936 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off.");
5937
5938 nsPIDOMWindowOuter* window = GetWindow();
5939 if (!window) {
5940 return NS_ERROR_FAILURE;
5941 }
5942
5943 nsIDocShell* docshell = window->GetDocShell();
5944 if (!docshell) {
5945 return NS_ERROR_FAILURE;
5946 }
5947
5948 bool isBeingDestroyed = false;
5949 docshell->IsBeingDestroyed(&isBeingDestroyed);
5950 if (isBeingDestroyed) {
5951 return NS_ERROR_FAILURE;
5952 }
5953
5954 nsCOMPtr<nsIEditingSession> editSession;
5955 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
5956 NS_ENSURE_SUCCESS(rv, rv);
5957
5958 // turn editing off
5959 rv = editSession->TearDownEditorOnWindow(window);
5960 NS_ENSURE_SUCCESS(rv, rv);
5961
5962 mEditingState = EditingState::eOff;
5963
5964 // Editor resets selection since it is being destroyed. But if focus is
5965 // still into editable control, we have to initialize selection again.
5966 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
5967 if (RefPtr<TextControlElement> textControlElement =
5968 TextControlElement::FromNodeOrNull(fm->GetFocusedElement())) {
5969 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) {
5970 textEditor->ReinitializeSelection(*textControlElement);
5971 }
5972 }
5973 }
5974
5975 return NS_OK;
5976 }
5977
HasPresShell(nsPIDOMWindowOuter * aWindow)5978 static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
5979 nsIDocShell* docShell = aWindow->GetDocShell();
5980 if (!docShell) {
5981 return false;
5982 }
5983 return docShell->GetPresShell() != nullptr;
5984 }
5985
EditingStateChanged()5986 nsresult Document::EditingStateChanged() {
5987 if (mRemovedFromDocShell) {
5988 return NS_OK;
5989 }
5990
5991 if (mEditingState == EditingState::eSettingUp ||
5992 mEditingState == EditingState::eTearingDown) {
5993 // XXX We shouldn't recurse
5994 return NS_OK;
5995 }
5996
5997 const bool designMode = IsInDesignMode();
5998 EditingState newState =
5999 designMode ? EditingState::eDesignMode
6000 : (mContentEditableCount > 0 ? EditingState::eContentEditable
6001 : EditingState::eOff);
6002 if (mEditingState == newState) {
6003 // No changes in editing mode.
6004 return NS_OK;
6005 }
6006
6007 if (newState == EditingState::eOff) {
6008 // Editing is being turned off.
6009 nsAutoScriptBlocker scriptBlocker;
6010 NotifyEditableStateChange(*this);
6011 return TurnEditingOff();
6012 }
6013
6014 // Flush out style changes on our _parent_ document, if any, so that
6015 // our check for a presshell won't get stale information.
6016 if (mParentDocument) {
6017 mParentDocument->FlushPendingNotifications(FlushType::Style);
6018 }
6019
6020 // get editing session, make sure this is a strong reference so the
6021 // window can't get deleted during the rest of this call.
6022 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6023 if (!window) {
6024 return NS_ERROR_FAILURE;
6025 }
6026
6027 nsIDocShell* docshell = window->GetDocShell();
6028 if (!docshell) {
6029 return NS_ERROR_FAILURE;
6030 }
6031
6032 // FlushPendingNotifications might destroy our docshell.
6033 bool isBeingDestroyed = false;
6034 docshell->IsBeingDestroyed(&isBeingDestroyed);
6035 if (isBeingDestroyed) {
6036 return NS_ERROR_FAILURE;
6037 }
6038
6039 nsCOMPtr<nsIEditingSession> editSession;
6040 nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
6041 NS_ENSURE_SUCCESS(rv, rv);
6042
6043 RefPtr<HTMLEditor> htmlEditor = editSession->GetHTMLEditorForWindow(window);
6044 if (htmlEditor) {
6045 // We might already have an editor if it was set up for mail, let's see
6046 // if this is actually the case.
6047 uint32_t flags = 0;
6048 htmlEditor->GetFlags(&flags);
6049 if (flags & nsIEditor::eEditorMailMask) {
6050 // We already have a mail editor, then we should not attempt to create
6051 // another one.
6052 return NS_OK;
6053 }
6054 }
6055
6056 if (!HasPresShell(window)) {
6057 // We should not make the window editable or setup its editor.
6058 // It's probably style=display:none.
6059 return NS_OK;
6060 }
6061
6062 bool makeWindowEditable = mEditingState == EditingState::eOff;
6063 bool spellRecheckAll = false;
6064 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
6065 htmlEditor = nullptr;
6066
6067 {
6068 EditingState oldState = mEditingState;
6069 nsAutoEditingState push(this, EditingState::eSettingUp);
6070
6071 RefPtr<PresShell> presShell = GetPresShell();
6072 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
6073
6074 // If we're entering the design mode from non-editable state, put the
6075 // selection at the beginning of the document for compatibility reasons.
6076 bool collapseSelectionAtBeginningOfDocument =
6077 designMode && oldState == EditingState::eOff;
6078 // However, mEditingState may be eOff even if there is some
6079 // `contenteditable` area and selection has been initialized for it because
6080 // mEditingState for `contenteditable` may have been scheduled to modify
6081 // when safe. In such case, we should not reinitialize selection.
6082 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) {
6083 Selection* selection =
6084 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
6085 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?");
6086 if (selection && selection->RangeCount()) {
6087 // Perhaps, we don't need to check whether the selection is in
6088 // an editing host or not because all contents will be editable
6089 // in designMode. (And we don't want to make this code so complicated
6090 // because of legacy API.)
6091 collapseSelectionAtBeginningOfDocument = false;
6092 }
6093 }
6094
6095 MOZ_ASSERT(mStyleSetFilled);
6096
6097 // Before making this window editable, we need to modify UA style sheet
6098 // because new style may change whether focused element will be focusable
6099 // or not.
6100 if (IsHTMLOrXHTML()) {
6101 AddContentEditableStyleSheetsToStyleSet(designMode);
6102 }
6103
6104 if (designMode) {
6105 // designMode is being turned on (overrides contentEditable).
6106 spellRecheckAll = oldState == EditingState::eContentEditable;
6107 }
6108
6109 // Adjust focused element with new style but blur event shouldn't be fired
6110 // until mEditingState is modified with newState.
6111 nsAutoScriptBlocker scriptBlocker;
6112 if (designMode) {
6113 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6114 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6115 window, nsFocusManager::eOnlyCurrentWindow,
6116 getter_AddRefs(focusedWindow));
6117 if (focusedContent) {
6118 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
6119 bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable()
6120 : !focusedContent->IsFocusable();
6121 if (clearFocus) {
6122 nsFocusManager* fm = nsFocusManager::GetFocusManager();
6123 if (fm) {
6124 fm->ClearFocus(window);
6125 // If we need to dispatch blur event, we should put off after
6126 // modifying mEditingState since blur event handler may change
6127 // designMode state again.
6128 putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
6129 }
6130 }
6131 }
6132 }
6133
6134 if (makeWindowEditable) {
6135 // Editing is being turned on (through designMode or contentEditable)
6136 // Turn on editor.
6137 // XXX This can cause flushing which can change the editing state, so make
6138 // sure to avoid recursing.
6139 rv = editSession->MakeWindowEditable(window, "html", false, false, true);
6140 NS_ENSURE_SUCCESS(rv, rv);
6141 }
6142
6143 // XXX Need to call TearDownEditorOnWindow for all failures.
6144 htmlEditor = docshell->GetHTMLEditor();
6145 if (!htmlEditor) {
6146 // Return NS_OK even though we've failed to create an editor here. This
6147 // is so that the setter of designMode on non-HTML documents does not
6148 // fail.
6149 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we
6150 // would detect that we can't support the mimetype if appropriate and
6151 // would fall onto the eEditorErrorCantEditMimeType path.
6152 return NS_OK;
6153 }
6154
6155 if (collapseSelectionAtBeginningOfDocument) {
6156 htmlEditor->BeginningOfDocument();
6157 }
6158
6159 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6160 nsContentUtils::AddScriptBlocker();
6161 }
6162 }
6163
6164 mEditingState = newState;
6165 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
6166 nsContentUtils::RemoveScriptBlocker();
6167 // If mEditingState is overwritten by another call and already disabled
6168 // the editing, we shouldn't keep making window editable.
6169 if (mEditingState == EditingState::eOff) {
6170 return NS_OK;
6171 }
6172 }
6173
6174 if (makeWindowEditable) {
6175 // TODO: We should do this earlier in this method.
6176 // Previously, we called `ExecCommand` with `insertBrOnReturn` command
6177 // whose argument is false here. Then, if it returns error, we
6178 // stopped making it editable. However, after bug 1697078 fixed,
6179 // `ExecCommand` returns error only when the document is not XHTML's
6180 // nor HTML's. Therefore, we use same error handling for now.
6181 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) {
6182 // Editor setup failed. Editing is not on after all.
6183 // XXX Should we reset the editable flag on nodes?
6184 editSession->TearDownEditorOnWindow(window);
6185 mEditingState = EditingState::eOff;
6186 return NS_ERROR_DOM_INVALID_STATE_ERR;
6187 }
6188 // Set the editor to not insert <br> elements on return when in <p> elements
6189 // by default.
6190 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true);
6191 }
6192
6193 // Resync the editor's spellcheck state.
6194 if (spellRecheckAll) {
6195 nsCOMPtr<nsISelectionController> selectionController =
6196 htmlEditor->GetSelectionController();
6197 if (NS_WARN_IF(!selectionController)) {
6198 return NS_ERROR_FAILURE;
6199 }
6200
6201 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection(
6202 nsISelectionController::SELECTION_SPELLCHECK);
6203 if (spellCheckSelection) {
6204 spellCheckSelection->RemoveAllRanges(IgnoreErrors());
6205 }
6206 }
6207 htmlEditor->SyncRealTimeSpell();
6208
6209 MaybeDispatchCheckKeyPressEventModelEvent();
6210
6211 return NS_OK;
6212 }
6213
6214 // Helper class, used below in ChangeContentEditableCount().
6215 class DeferredContentEditableCountChangeEvent : public Runnable {
6216 public:
DeferredContentEditableCountChangeEvent(Document * aDoc,Element * aElement)6217 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement)
6218 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"),
6219 mDoc(aDoc),
6220 mElement(aElement) {}
6221
Run()6222 NS_IMETHOD Run() override {
6223 if (mElement && mElement->OwnerDoc() == mDoc) {
6224 mDoc->DeferredContentEditableCountChange(mElement);
6225 }
6226 return NS_OK;
6227 }
6228
6229 private:
6230 RefPtr<Document> mDoc;
6231 RefPtr<Element> mElement;
6232 };
6233
ChangeContentEditableCount(Element * aElement,int32_t aChange)6234 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
6235 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
6236 "Trying to decrement too much.");
6237
6238 mContentEditableCount += aChange;
6239
6240 nsContentUtils::AddScriptRunner(
6241 new DeferredContentEditableCountChangeEvent(this, aElement));
6242 }
6243
DeferredContentEditableCountChange(Element * aElement)6244 void Document::DeferredContentEditableCountChange(Element* aElement) {
6245 if (mParser ||
6246 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
6247 return;
6248 }
6249
6250 EditingState oldState = mEditingState;
6251
6252 nsresult rv = EditingStateChanged();
6253 NS_ENSURE_SUCCESS_VOID(rv);
6254
6255 if (oldState == mEditingState &&
6256 mEditingState == EditingState::eContentEditable) {
6257 // We just changed the contentEditable state of a node, we need to reset
6258 // the spellchecking state of that node.
6259 if (aElement) {
6260 nsPIDOMWindowOuter* window = GetWindow();
6261 if (!window) {
6262 return;
6263 }
6264
6265 nsIDocShell* docshell = window->GetDocShell();
6266 if (!docshell) {
6267 return;
6268 }
6269
6270 RefPtr<HTMLEditor> htmlEditor = docshell->GetHTMLEditor();
6271 if (htmlEditor) {
6272 RefPtr<nsRange> range = nsRange::Create(aElement);
6273 IgnoredErrorResult res;
6274 range->SelectNode(*aElement, res);
6275 if (res.Failed()) {
6276 // The node might be detached from the document at this point,
6277 // which would cause this call to fail. In this case, we can
6278 // safely ignore the contenteditable count change.
6279 return;
6280 }
6281
6282 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
6283 rv = htmlEditor->GetInlineSpellChecker(false,
6284 getter_AddRefs(spellChecker));
6285 NS_ENSURE_SUCCESS_VOID(rv);
6286
6287 if (spellChecker) {
6288 rv = spellChecker->SpellCheckRange(range);
6289 NS_ENSURE_SUCCESS_VOID(rv);
6290 }
6291 }
6292 }
6293 }
6294 }
6295
MaybeDispatchCheckKeyPressEventModelEvent()6296 void Document::MaybeDispatchCheckKeyPressEventModelEvent() {
6297 // Currently, we need to check only when we're becoming editable for
6298 // contenteditable.
6299 if (mEditingState != EditingState::eContentEditable) {
6300 return;
6301 }
6302
6303 if (mHasBeenEditable) {
6304 return;
6305 }
6306 mHasBeenEditable = true;
6307
6308 // Dispatch "CheckKeyPressEventModel" event. That is handled only by
6309 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
6310 // with proper keypress event for the active web app.
6311 WidgetEvent checkEvent(true, eUnidentifiedEvent);
6312 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
6313 checkEvent.mFlags.mCancelable = false;
6314 checkEvent.mFlags.mBubbles = false;
6315 checkEvent.mFlags.mOnlySystemGroupDispatch = true;
6316 // Post the event rather than dispatching it synchronously because we need
6317 // a call of SetKeyPressEventModel() before first key input. Therefore, we
6318 // can avoid paying unnecessary runtime cost for most web apps.
6319 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
6320 }
6321
SetKeyPressEventModel(uint16_t aKeyPressEventModel)6322 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
6323 PresShell* presShell = GetPresShell();
6324 if (!presShell) {
6325 return;
6326 }
6327 presShell->SetKeyPressEventModel(aKeyPressEventModel);
6328 }
6329
LastFocusTime() const6330 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; }
6331
SetLastFocusTime(const TimeStamp & aFocusTime)6332 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) {
6333 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull());
6334 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() ||
6335 aFocusTime >= mLastFocusTime);
6336 mLastFocusTime = aFocusTime;
6337 }
6338
GetReferrer(nsAString & aReferrer) const6339 void Document::GetReferrer(nsAString& aReferrer) const {
6340 aReferrer.Truncate();
6341 if (!mReferrerInfo) {
6342 return;
6343 }
6344
6345 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
6346 if (!referrer) {
6347 return;
6348 }
6349
6350 nsAutoCString uri;
6351 nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
6352 if (NS_WARN_IF(NS_FAILED(rv))) {
6353 return;
6354 }
6355
6356 CopyUTF8toUTF16(uri, aReferrer);
6357 }
6358
GetCookie(nsAString & aCookie,ErrorResult & rv)6359 void Document::GetCookie(nsAString& aCookie, ErrorResult& rv) {
6360 aCookie.Truncate(); // clear current cookie in case service fails;
6361 // no cookie isn't an error condition.
6362
6363 if (mDisableCookieAccess) {
6364 return;
6365 }
6366
6367 // If the document's sandboxed origin flag is set, access to read cookies
6368 // is prohibited.
6369 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6370 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
6371 return;
6372 }
6373
6374 StorageAccess storageAccess = StorageAllowedForDocument(this);
6375 if (storageAccess == StorageAccess::eDeny) {
6376 return;
6377 }
6378
6379 if (ShouldPartitionStorage(storageAccess) &&
6380 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6381 return;
6382 }
6383
6384 // If the document is a cookie-averse Document... return the empty string.
6385 if (IsCookieAverse()) {
6386 return;
6387 }
6388
6389 // not having a cookie service isn't an error
6390 nsCOMPtr<nsICookieService> service =
6391 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6392 if (service) {
6393 nsAutoCString cookie;
6394 service->GetCookieStringFromDocument(this, cookie);
6395 // CopyUTF8toUTF16 doesn't handle error
6396 // because it assumes that the input is valid.
6397 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
6398 }
6399 }
6400
SetCookie(const nsAString & aCookie,ErrorResult & aRv)6401 void Document::SetCookie(const nsAString& aCookie, ErrorResult& aRv) {
6402 if (mDisableCookieAccess) {
6403 return;
6404 }
6405
6406 // If the document's sandboxed origin flag is set, access to write cookies
6407 // is prohibited.
6408 if (mSandboxFlags & SANDBOXED_ORIGIN) {
6409 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
6410 return;
6411 }
6412
6413 StorageAccess storageAccess = StorageAllowedForDocument(this);
6414 if (storageAccess == StorageAccess::eDeny) {
6415 return;
6416 }
6417
6418 if (ShouldPartitionStorage(storageAccess) &&
6419 !StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
6420 return;
6421 }
6422
6423 // If the document is a cookie-averse Document... do nothing.
6424 if (IsCookieAverse()) {
6425 return;
6426 }
6427
6428 if (!mDocumentURI) {
6429 return;
6430 }
6431
6432 // not having a cookie service isn't an error
6433 nsCOMPtr<nsICookieService> service =
6434 do_GetService(NS_COOKIESERVICE_CONTRACTID);
6435 if (!service) {
6436 return;
6437 }
6438
6439 NS_ConvertUTF16toUTF8 cookie(aCookie);
6440 nsresult rv = service->SetCookieStringFromDocument(this, cookie);
6441
6442 // No warning messages here.
6443 if (NS_FAILED(rv)) {
6444 return;
6445 }
6446
6447 nsCOMPtr<nsIObserverService> observerService =
6448 mozilla::services::GetObserverService();
6449 if (observerService) {
6450 observerService->NotifyObservers(ToSupports(this), "document-set-cookie",
6451 nsString(aCookie).get());
6452 }
6453 }
6454
GetReferrerPolicy() const6455 ReferrerPolicy Document::GetReferrerPolicy() const {
6456 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy()
6457 : ReferrerPolicy::_empty;
6458 }
6459
GetAlinkColor(nsAString & aAlinkColor)6460 void Document::GetAlinkColor(nsAString& aAlinkColor) {
6461 aAlinkColor.Truncate();
6462
6463 HTMLBodyElement* body = GetBodyElement();
6464 if (body) {
6465 body->GetALink(aAlinkColor);
6466 }
6467 }
6468
SetAlinkColor(const nsAString & aAlinkColor)6469 void Document::SetAlinkColor(const nsAString& aAlinkColor) {
6470 HTMLBodyElement* body = GetBodyElement();
6471 if (body) {
6472 body->SetALink(aAlinkColor);
6473 }
6474 }
6475
GetLinkColor(nsAString & aLinkColor)6476 void Document::GetLinkColor(nsAString& aLinkColor) {
6477 aLinkColor.Truncate();
6478
6479 HTMLBodyElement* body = GetBodyElement();
6480 if (body) {
6481 body->GetLink(aLinkColor);
6482 }
6483 }
6484
SetLinkColor(const nsAString & aLinkColor)6485 void Document::SetLinkColor(const nsAString& aLinkColor) {
6486 HTMLBodyElement* body = GetBodyElement();
6487 if (body) {
6488 body->SetLink(aLinkColor);
6489 }
6490 }
6491
GetVlinkColor(nsAString & aVlinkColor)6492 void Document::GetVlinkColor(nsAString& aVlinkColor) {
6493 aVlinkColor.Truncate();
6494
6495 HTMLBodyElement* body = GetBodyElement();
6496 if (body) {
6497 body->GetVLink(aVlinkColor);
6498 }
6499 }
6500
SetVlinkColor(const nsAString & aVlinkColor)6501 void Document::SetVlinkColor(const nsAString& aVlinkColor) {
6502 HTMLBodyElement* body = GetBodyElement();
6503 if (body) {
6504 body->SetVLink(aVlinkColor);
6505 }
6506 }
6507
GetBgColor(nsAString & aBgColor)6508 void Document::GetBgColor(nsAString& aBgColor) {
6509 aBgColor.Truncate();
6510
6511 HTMLBodyElement* body = GetBodyElement();
6512 if (body) {
6513 body->GetBgColor(aBgColor);
6514 }
6515 }
6516
SetBgColor(const nsAString & aBgColor)6517 void Document::SetBgColor(const nsAString& aBgColor) {
6518 HTMLBodyElement* body = GetBodyElement();
6519 if (body) {
6520 body->SetBgColor(aBgColor);
6521 }
6522 }
6523
GetFgColor(nsAString & aFgColor)6524 void Document::GetFgColor(nsAString& aFgColor) {
6525 aFgColor.Truncate();
6526
6527 HTMLBodyElement* body = GetBodyElement();
6528 if (body) {
6529 body->GetText(aFgColor);
6530 }
6531 }
6532
SetFgColor(const nsAString & aFgColor)6533 void Document::SetFgColor(const nsAString& aFgColor) {
6534 HTMLBodyElement* body = GetBodyElement();
6535 if (body) {
6536 body->SetText(aFgColor);
6537 }
6538 }
6539
CaptureEvents()6540 void Document::CaptureEvents() {
6541 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
6542 }
6543
ReleaseEvents()6544 void Document::ReleaseEvents() {
6545 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
6546 }
6547
All()6548 HTMLAllCollection* Document::All() {
6549 if (!mAll) {
6550 mAll = new HTMLAllCollection(this);
6551 }
6552 return mAll;
6553 }
6554
GetSrcdocData(nsAString & aSrcdocData)6555 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) {
6556 if (mIsSrcdocDocument) {
6557 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
6558 if (inStrmChan) {
6559 return inStrmChan->GetSrcdocData(aSrcdocData);
6560 }
6561 }
6562 aSrcdocData = VoidString();
6563 return NS_OK;
6564 }
6565
GetDefaultView() const6566 Nullable<WindowProxyHolder> Document::GetDefaultView() const {
6567 nsPIDOMWindowOuter* win = GetWindow();
6568 if (!win) {
6569 return nullptr;
6570 }
6571 return WindowProxyHolder(win->GetBrowsingContext());
6572 }
6573
GetUnretargetedFocusedContent() const6574 nsIContent* Document::GetUnretargetedFocusedContent() const {
6575 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
6576 if (!window) {
6577 return nullptr;
6578 }
6579 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6580 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant(
6581 window, nsFocusManager::eOnlyCurrentWindow,
6582 getter_AddRefs(focusedWindow));
6583 if (!focusedContent) {
6584 return nullptr;
6585 }
6586 // be safe and make sure the element is from this document
6587 if (focusedContent->OwnerDoc() != this) {
6588 return nullptr;
6589 }
6590
6591 if (focusedContent->ChromeOnlyAccess()) {
6592 return focusedContent->FindFirstNonChromeOnlyAccessContent();
6593 }
6594 return focusedContent;
6595 }
6596
GetActiveElement()6597 Element* Document::GetActiveElement() {
6598 // Get the focused element.
6599 Element* focusedElement = GetRetargetedFocusedElement();
6600 if (focusedElement) {
6601 return focusedElement;
6602 }
6603
6604 // No focused element anywhere in this document. Try to get the BODY.
6605 if (IsHTMLOrXHTML()) {
6606 Element* bodyElement = AsHTMLDocument()->GetBody();
6607 if (bodyElement) {
6608 return bodyElement;
6609 }
6610 // Special case to handle the transition to XHTML from XUL documents
6611 // where there currently isn't a body element, but we need to match the
6612 // XUL behavior. This should be removed when bug 1540278 is resolved.
6613 if (nsContentUtils::IsChromeDoc(this)) {
6614 Element* docElement = GetDocumentElement();
6615 if (docElement && docElement->IsXULElement()) {
6616 return docElement;
6617 }
6618 }
6619 // Because of IE compatibility, return null when html document doesn't have
6620 // a body.
6621 return nullptr;
6622 }
6623
6624 // If we couldn't get a BODY, return the root element.
6625 return GetDocumentElement();
6626 }
6627
GetCurrentScript()6628 Element* Document::GetCurrentScript() {
6629 nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
6630 return el;
6631 }
6632
ReleaseCapture() const6633 void Document::ReleaseCapture() const {
6634 // only release the capture if the caller can access it. This prevents a
6635 // page from stopping a scrollbar grab for example.
6636 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
6637 if (node && nsContentUtils::CanCallerAccess(node)) {
6638 PresShell::ReleaseCapturingContent();
6639 }
6640 }
6641
GetBaseURI(bool aTryUseXHRDocBaseURI) const6642 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
6643 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
6644 return mChromeXHRDocBaseURI;
6645 }
6646
6647 return GetDocBaseURI();
6648 }
6649
SetBaseURI(nsIURI * aURI)6650 void Document::SetBaseURI(nsIURI* aURI) {
6651 if (!aURI && !mDocumentBaseURI) {
6652 return;
6653 }
6654
6655 // Don't do anything if the URI wasn't actually changed.
6656 if (aURI && mDocumentBaseURI) {
6657 bool equalBases = false;
6658 mDocumentBaseURI->Equals(aURI, &equalBases);
6659 if (equalBases) {
6660 return;
6661 }
6662 }
6663
6664 mDocumentBaseURI = aURI;
6665 RefreshLinkHrefs();
6666 }
6667
ResolveWithBaseURI(const nsAString & aURI)6668 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI(
6669 const nsAString& aURI) {
6670 RefPtr<nsIURI> resolvedURI;
6671 MOZ_TRY(
6672 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI()));
6673 return OwningNonNull<nsIURI>(std::move(resolvedURI));
6674 }
6675
DefaultStyleAttrURLData()6676 URLExtraData* Document::DefaultStyleAttrURLData() {
6677 MOZ_ASSERT(NS_IsMainThread());
6678 nsIURI* baseURI = GetDocBaseURI();
6679 nsIPrincipal* principal = NodePrincipal();
6680 bool equals;
6681 if (!mCachedURLData || mCachedURLData->BaseURI() != baseURI ||
6682 mCachedURLData->Principal() != principal || !mCachedReferrerInfo ||
6683 NS_FAILED(mCachedURLData->ReferrerInfo()->Equals(mCachedReferrerInfo,
6684 &equals)) ||
6685 !equals) {
6686 mCachedReferrerInfo = ReferrerInfo::CreateForInternalCSSResources(this);
6687 mCachedURLData = new URLExtraData(baseURI, mCachedReferrerInfo, principal);
6688 }
6689 return mCachedURLData;
6690 }
6691
SetDocumentCharacterSet(NotNull<const Encoding * > aEncoding)6692 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) {
6693 if (mCharacterSet != aEncoding) {
6694 mCharacterSet = aEncoding;
6695 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING;
6696 RecomputeLanguageFromCharset();
6697
6698 if (nsPresContext* context = GetPresContext()) {
6699 context->DocumentCharSetChanged(aEncoding);
6700 }
6701 }
6702 }
6703
GetSandboxFlagsAsString(nsAString & aFlags)6704 void Document::GetSandboxFlagsAsString(nsAString& aFlags) {
6705 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
6706 }
6707
GetHeaderData(nsAtom * aHeaderField,nsAString & aData) const6708 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
6709 aData.Truncate();
6710 const HeaderData* data = mHeaderData.get();
6711 while (data) {
6712 if (data->mField == aHeaderField) {
6713 aData = data->mData;
6714 break;
6715 }
6716 data = data->mNext.get();
6717 }
6718 }
6719
SetHeaderData(nsAtom * aHeaderField,const nsAString & aData)6720 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
6721 if (!aHeaderField) {
6722 NS_ERROR("null headerField");
6723 return;
6724 }
6725
6726 if (!mHeaderData) {
6727 if (!aData.IsEmpty()) { // don't bother storing empty string
6728 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData);
6729 }
6730 } else {
6731 HeaderData* data = mHeaderData.get();
6732 UniquePtr<HeaderData>* lastPtr = &mHeaderData;
6733 bool found = false;
6734 do { // look for existing and replace
6735 if (data->mField == aHeaderField) {
6736 if (!aData.IsEmpty()) {
6737 data->mData.Assign(aData);
6738 } else { // don't store empty string
6739 // Note that data->mNext is moved to a temporary before the old value
6740 // of *lastPtr is deleted.
6741 *lastPtr = std::move(data->mNext);
6742 }
6743 found = true;
6744
6745 break;
6746 }
6747 lastPtr = &data->mNext;
6748 data = lastPtr->get();
6749 } while (data);
6750
6751 if (!aData.IsEmpty() && !found) {
6752 // didn't find, append
6753 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData);
6754 }
6755 }
6756
6757 if (aHeaderField == nsGkAtoms::headerContentLanguage) {
6758 CopyUTF16toUTF8(aData, mContentLanguage);
6759 mMayNeedFontPrefsUpdate = true;
6760 if (auto* presContext = GetPresContext()) {
6761 presContext->ContentLanguageChanged();
6762 }
6763 }
6764
6765 if (aHeaderField == nsGkAtoms::origin_trial) {
6766 mTrials.UpdateFromToken(aData, NodePrincipal());
6767 }
6768
6769 if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
6770 SetPreferredStyleSheetSet(aData);
6771 }
6772
6773 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) {
6774 // We get into this code before we have a script global yet, so get to our
6775 // container via mDocumentContainer.
6776 if (mDocumentContainer) {
6777 // Note: using mDocumentURI instead of mBaseURI here, for consistency
6778 // (used to just use the current URI of our webnavigation, but that
6779 // should really be the same thing). Note that this code can run
6780 // before the current URI of the webnavigation has been updated, so we
6781 // can't assert equality here.
6782 mDocumentContainer->SetupRefreshURIFromHeader(this, aData);
6783 }
6784 }
6785
6786 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl &&
6787 mAllowDNSPrefetch) {
6788 // Chromium treats any value other than 'on' (case insensitive) as 'off'.
6789 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
6790 }
6791
6792 if (aHeaderField == nsGkAtoms::handheldFriendly) {
6793 mViewportType = Unknown;
6794 }
6795 }
6796
TryChannelCharset(nsIChannel * aChannel,int32_t & aCharsetSource,NotNull<const Encoding * > & aEncoding,nsHtml5TreeOpExecutor * aExecutor)6797 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
6798 NotNull<const Encoding*>& aEncoding,
6799 nsHtml5TreeOpExecutor* aExecutor) {
6800 if (aChannel) {
6801 nsAutoCString charsetVal;
6802 nsresult rv = aChannel->GetContentCharset(charsetVal);
6803 if (NS_SUCCEEDED(rv)) {
6804 const Encoding* preferred = Encoding::ForLabel(charsetVal);
6805 if (preferred) {
6806 if (aExecutor && preferred == REPLACEMENT_ENCODING) {
6807 aExecutor->ComplainAboutBogusProtocolCharset(this, false);
6808 }
6809 aEncoding = WrapNotNull(preferred);
6810 aCharsetSource = kCharsetFromChannel;
6811 return;
6812 } else if (aExecutor && !charsetVal.IsEmpty()) {
6813 aExecutor->ComplainAboutBogusProtocolCharset(this, true);
6814 }
6815 }
6816 }
6817 }
6818
AssertNoStaleServoDataIn(nsINode & aSubtreeRoot)6819 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) {
6820 #ifdef DEBUG
6821 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) {
6822 const Element* element = Element::FromNode(node);
6823 if (!element) {
6824 continue;
6825 }
6826 MOZ_ASSERT(!element->HasServoData());
6827 }
6828 #endif
6829 }
6830
CreatePresShell(nsPresContext * aContext,nsViewManager * aViewManager)6831 already_AddRefed<PresShell> Document::CreatePresShell(
6832 nsPresContext* aContext, nsViewManager* aViewManager) {
6833 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!");
6834
6835 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
6836
6837 AssertNoStaleServoDataIn(*this);
6838
6839 RefPtr<PresShell> presShell = new PresShell(this);
6840 // Note: we don't hold a ref to the shell (it holds a ref to us)
6841 mPresShell = presShell;
6842
6843 if (!mStyleSetFilled) {
6844 FillStyleSet();
6845 }
6846
6847 presShell->Init(aContext, aViewManager);
6848
6849 // Gaining a shell causes changes in how media queries are evaluated, so
6850 // invalidate that.
6851 aContext->MediaFeatureValuesChanged(
6852 {MediaFeatureChange::kAllChanges},
6853 MediaFeatureChangePropagation::JustThisDocument);
6854
6855 // Make sure to never paint if we belong to an invisible DocShell.
6856 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
6857 if (docShell && docShell->IsInvisible()) {
6858 presShell->SetNeverPainting(true);
6859 }
6860
6861 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
6862 ("DOCUMENT %p with PressShell %p and DocShell %p", this,
6863 presShell.get(), docShell.get()));
6864
6865 mExternalResourceMap.ShowViewers();
6866
6867 UpdateFrameRequestCallbackSchedulingState();
6868
6869 if (mDocumentL10n) {
6870 // In case we already accumulated mutations,
6871 // we'll trigger the refresh driver now.
6872 mDocumentL10n->OnCreatePresShell();
6873 }
6874
6875 // Now that we have a shell, we might have @font-face rules (the presence of a
6876 // shell may change which rules apply to us). We don't need to do anything
6877 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff
6878 // is ready to update we'll flush the font set.
6879 MarkUserFontSetDirty();
6880
6881 // Take the author style disabled state from the top browsing cvontext.
6882 // (PageStyleChild.jsm ensures this is up to date.)
6883 if (BrowsingContext* bc = GetBrowsingContext()) {
6884 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault());
6885 }
6886
6887 return presShell.forget();
6888 }
6889
UpdateFrameRequestCallbackSchedulingState(PresShell * aOldPresShell)6890 void Document::UpdateFrameRequestCallbackSchedulingState(
6891 PresShell* aOldPresShell) {
6892 // If this condition changes to depend on some other variable, make sure to
6893 // call UpdateFrameRequestCallbackSchedulingState() calls to the places where
6894 // that variable can change. Also consider if you should change
6895 // WouldScheduleFrameRequestCallbacks() instead of adding more stuff to this
6896 // condition.
6897 bool shouldBeScheduled =
6898 WouldScheduleFrameRequestCallbacks() && !mFrameRequestManager.IsEmpty();
6899 if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
6900 // nothing to do
6901 return;
6902 }
6903
6904 PresShell* presShell = aOldPresShell ? aOldPresShell : mPresShell;
6905 MOZ_RELEASE_ASSERT(presShell);
6906
6907 nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
6908 if (shouldBeScheduled) {
6909 rd->ScheduleFrameRequestCallbacks(this);
6910 } else {
6911 rd->RevokeFrameRequestCallbacks(this);
6912 }
6913
6914 mFrameRequestCallbacksScheduled = shouldBeScheduled;
6915 }
6916
TakeFrameRequestCallbacks(nsTArray<FrameRequest> & aCallbacks)6917 void Document::TakeFrameRequestCallbacks(nsTArray<FrameRequest>& aCallbacks) {
6918 MOZ_ASSERT(aCallbacks.IsEmpty());
6919 mFrameRequestManager.Take(aCallbacks);
6920 // No need to manually remove ourselves from the refresh driver; it will
6921 // handle that part. But we do have to update our state.
6922 mFrameRequestCallbacksScheduled = false;
6923 }
6924
ShouldThrottleFrameRequests() const6925 bool Document::ShouldThrottleFrameRequests() const {
6926 if (mStaticCloneCount > 0) {
6927 // Even if we're not visible, a static clone may be, so run at full speed.
6928 return false;
6929 }
6930
6931 if (Hidden()) {
6932 // We're not visible (probably in a background tab or the bf cache).
6933 return true;
6934 }
6935
6936 if (!mPresShell) {
6937 return false; // Can't do anything smarter.
6938 }
6939
6940 if (!mPresShell->IsActive()) {
6941 // The pres shell is not active (we're an invisible OOP iframe or such), so
6942 // throttle.
6943 return true;
6944 }
6945
6946 nsIFrame* frame = mPresShell->GetRootFrame();
6947 if (!frame) {
6948 return false; // Can't do anything smarter.
6949 }
6950
6951 nsIFrame* displayRootFrame = nsLayoutUtils::GetDisplayRootFrame(frame);
6952 if (!displayRootFrame) {
6953 return false; // Can't do anything smarter.
6954 }
6955
6956 if (!displayRootFrame->DidPaintPresShell(mPresShell)) {
6957 // We didn't get painted during the last paint, so we're not visible.
6958 // Throttle. Note that because we have to paint this document at least
6959 // once to unthrottle it, we will drop one requestAnimationFrame frame
6960 // when a document that previously wasn't visible scrolls into view. This
6961 // is acceptable since it would happen outside the viewport on APZ
6962 // platforms and is unlikely to be human-perceivable on non-APZ platforms.
6963 return true;
6964 }
6965
6966 // We got painted during the last paint, so run at full speed.
6967 return false;
6968 }
6969
DeletePresShell()6970 void Document::DeletePresShell() {
6971 mExternalResourceMap.HideViewers();
6972 if (nsPresContext* presContext = mPresShell->GetPresContext()) {
6973 presContext->RefreshDriver()->CancelPendingFullscreenEvents(this);
6974 }
6975
6976 // When our shell goes away, request that all our images be immediately
6977 // discarded, so we don't carry around decoded image data for a document we
6978 // no longer intend to paint.
6979 ImageTracker()->RequestDiscardAll();
6980
6981 // Now that we no longer have a shell, we need to forget about any FontFace
6982 // objects for @font-face rules that came from the style set. There's no need
6983 // to call EnsureStyleFlush either, the shell is going away anyway, so there's
6984 // no point on it.
6985 MarkUserFontSetDirty();
6986
6987 if (mResizeObserverController) {
6988 mResizeObserverController->ShellDetachedFromDocument();
6989 }
6990
6991 if (IsEditingOn()) {
6992 TurnEditingOff();
6993 }
6994
6995 PresShell* oldPresShell = mPresShell;
6996 mPresShell = nullptr;
6997 UpdateFrameRequestCallbackSchedulingState(oldPresShell);
6998
6999 ClearStaleServoData();
7000 AssertNoStaleServoDataIn(*this);
7001
7002 mStyleSet->ShellDetachedFromDocument();
7003 mStyleSetFilled = false;
7004 mQuirkSheetAdded = false;
7005 mContentEditableSheetAdded = false;
7006 mDesignModeSheetAdded = false;
7007 }
7008
DisallowBFCaching(uint32_t aStatus)7009 void Document::DisallowBFCaching(uint32_t aStatus) {
7010 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
7011 if (!mBFCacheDisallowed) {
7012 WindowGlobalChild* wgc = GetWindowGlobalChild();
7013 if (wgc) {
7014 wgc->SendUpdateBFCacheStatus(aStatus, 0);
7015 }
7016 }
7017 mBFCacheDisallowed = true;
7018 }
7019
SetBFCacheEntry(nsIBFCacheEntry * aEntry)7020 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) {
7021 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!");
7022
7023 if (mPresShell) {
7024 if (aEntry) {
7025 mPresShell->StopObservingRefreshDriver();
7026 } else if (mBFCacheEntry) {
7027 mPresShell->StartObservingRefreshDriver();
7028 }
7029 }
7030 mBFCacheEntry = aEntry;
7031 }
7032
RemoveFromBFCacheSync()7033 bool Document::RemoveFromBFCacheSync() {
7034 bool removed = false;
7035 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) {
7036 entry->RemoveFromBFCacheSync();
7037 removed = true;
7038 } else if (!IsCurrentActiveDocument()) {
7039 // In the old bfcache implementation while the new page is loading, but
7040 // before nsIContentViewer.show() has been called, the previous page doesn't
7041 // yet have nsIBFCacheEntry. However, the previous page isn't the current
7042 // active document anymore.
7043 DisallowBFCaching();
7044 removed = true;
7045 }
7046
7047 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) {
7048 if (BrowsingContext* bc = GetBrowsingContext()) {
7049 if (bc->IsInBFCache()) {
7050 ContentChild* cc = ContentChild::GetSingleton();
7051 // IPC is asynchronous but the caller is supposed to check the return
7052 // value. The reason for 'Sync' in the method name is that the old
7053 // implementation may run scripts. There is Async variant in
7054 // the old session history implementation for the cases where
7055 // synchronous operation isn't safe.
7056 cc->SendRemoveFromBFCache(bc->Top());
7057 removed = true;
7058 }
7059 }
7060 }
7061 return removed;
7062 }
7063
SubDocClearEntry(PLDHashTable * table,PLDHashEntryHdr * entry)7064 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) {
7065 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry);
7066
7067 NS_RELEASE(e->mKey);
7068 if (e->mSubDocument) {
7069 e->mSubDocument->SetParentDocument(nullptr);
7070 NS_RELEASE(e->mSubDocument);
7071 }
7072 }
7073
SubDocInitEntry(PLDHashEntryHdr * entry,const void * key)7074 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) {
7075 SubDocMapEntry* e =
7076 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry));
7077
7078 e->mKey = const_cast<Element*>(static_cast<const Element*>(key));
7079 NS_ADDREF(e->mKey);
7080
7081 e->mSubDocument = nullptr;
7082 }
7083
SetSubDocumentFor(Element * aElement,Document * aSubDoc)7084 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) {
7085 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
7086
7087 if (!aSubDoc) {
7088 // aSubDoc is nullptr, remove the mapping
7089
7090 if (mSubDocuments) {
7091 mSubDocuments->Remove(aElement);
7092 }
7093 } else {
7094 if (!mSubDocuments) {
7095 // Create a new hashtable
7096
7097 static const PLDHashTableOps hash_table_ops = {
7098 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
7099 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry};
7100
7101 mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
7102 }
7103
7104 // Add a mapping to the hash table
7105 auto entry =
7106 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible));
7107
7108 if (!entry) {
7109 return NS_ERROR_OUT_OF_MEMORY;
7110 }
7111
7112 if (entry->mSubDocument) {
7113 entry->mSubDocument->SetParentDocument(nullptr);
7114
7115 // Release the old sub document
7116 NS_RELEASE(entry->mSubDocument);
7117 }
7118
7119 entry->mSubDocument = aSubDoc;
7120 NS_ADDREF(entry->mSubDocument);
7121
7122 aSubDoc->SetParentDocument(this);
7123 }
7124
7125 return NS_OK;
7126 }
7127
GetSubDocumentFor(nsIContent * aContent) const7128 Document* Document::GetSubDocumentFor(nsIContent* aContent) const {
7129 if (mSubDocuments && aContent->IsElement()) {
7130 auto entry = static_cast<SubDocMapEntry*>(
7131 mSubDocuments->Search(aContent->AsElement()));
7132
7133 if (entry) {
7134 return entry->mSubDocument;
7135 }
7136 }
7137
7138 return nullptr;
7139 }
7140
GetEmbedderElement() const7141 Element* Document::GetEmbedderElement() const {
7142 // We check if we're the active document in our BrowsingContext
7143 // by comparing against its document, rather than checking if the
7144 // WindowContext is cached, since mWindow may be null when we're
7145 // called (such as in nsPresContext::Init).
7146 if (BrowsingContext* bc = GetBrowsingContext()) {
7147 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr;
7148 }
7149
7150 return nullptr;
7151 }
7152
IsNodeOfType(uint32_t aFlags) const7153 bool Document::IsNodeOfType(uint32_t aFlags) const { return false; }
7154
GetRootElement() const7155 Element* Document::GetRootElement() const {
7156 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
7157 ? mCachedRootElement
7158 : GetRootElementInternal();
7159 }
7160
GetUnfocusedKeyEventTarget()7161 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
7162
GetRootElementInternal() const7163 Element* Document::GetRootElementInternal() const {
7164 // We invoke GetRootElement() immediately before the servo traversal, so we
7165 // should always have a cache hit from Servo.
7166 MOZ_ASSERT(NS_IsMainThread());
7167
7168 // Loop backwards because any non-elements, such as doctypes and PIs
7169 // are likely to appear before the root element.
7170 for (nsIContent* child = GetLastChild(); child;
7171 child = child->GetPreviousSibling()) {
7172 if (Element* element = Element::FromNode(child)) {
7173 const_cast<Document*>(this)->mCachedRootElement = element;
7174 return element;
7175 }
7176 }
7177
7178 const_cast<Document*>(this)->mCachedRootElement = nullptr;
7179 return nullptr;
7180 }
7181
InsertChildBefore(nsIContent * aKid,nsIContent * aBeforeThis,bool aNotify,ErrorResult & aRv)7182 void Document::InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
7183 bool aNotify, ErrorResult& aRv) {
7184 if (aKid->IsElement() && GetRootElement()) {
7185 NS_WARNING("Inserting root element when we already have one");
7186 aRv.ThrowHierarchyRequestError("There is already a root element.");
7187 return;
7188 }
7189
7190 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv);
7191 }
7192
RemoveChildNode(nsIContent * aKid,bool aNotify)7193 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify) {
7194 Maybe<mozAutoDocUpdate> updateBatch;
7195 if (aKid->IsElement()) {
7196 updateBatch.emplace(this, aNotify);
7197 // Destroy the link map up front before we mess with the child list.
7198 DestroyElementMaps();
7199 }
7200
7201 // Preemptively clear mCachedRootElement, since we may be about to remove it
7202 // from our child list, and we don't want to return this maybe-obsolete value
7203 // from any GetRootElement() calls that happen inside of RemoveChildNode().
7204 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any
7205 // GetRootElement() calls until after it's removed the child from mChildren.
7206 // Any call before that point would restore this soon-to-be-obsolete cached
7207 // answer, and our clearing here would be fruitless.)
7208 mCachedRootElement = nullptr;
7209 nsINode::RemoveChildNode(aKid, aNotify);
7210 MOZ_ASSERT(mCachedRootElement != aKid,
7211 "Stale pointer in mCachedRootElement, after we tried to clear it "
7212 "(maybe somebody called GetRootElement() too early?)");
7213 }
7214
AddStyleSheetToStyleSets(StyleSheet & aSheet)7215 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) {
7216 if (mStyleSetFilled) {
7217 mStyleSet->AddDocStyleSheet(aSheet);
7218 ApplicableStylesChanged();
7219 }
7220 }
7221
RecordShadowStyleChange(ShadowRoot & aShadowRoot)7222 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) {
7223 mStyleSet->RecordShadowStyleChange(aShadowRoot);
7224 ApplicableStylesChanged();
7225 }
7226
ApplicableStylesChanged()7227 void Document::ApplicableStylesChanged() {
7228 // TODO(emilio): if we decide to resolve style in display: none iframes, then
7229 // we need to always track style changes and remove the mStyleSetFilled.
7230 if (!mStyleSetFilled) {
7231 return;
7232 }
7233
7234 MarkUserFontSetDirty();
7235 PresShell* ps = GetPresShell();
7236 if (!ps) {
7237 return;
7238 }
7239
7240 ps->EnsureStyleFlush();
7241 nsPresContext* pc = ps->GetPresContext();
7242 if (!pc) {
7243 return;
7244 }
7245
7246 pc->MarkCounterStylesDirty();
7247 pc->MarkFontFeatureValuesDirty();
7248 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges();
7249 }
7250
RemoveStyleSheetFromStyleSets(StyleSheet & aSheet)7251 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) {
7252 if (mStyleSetFilled) {
7253 mStyleSet->RemoveStyleSheet(aSheet);
7254 ApplicableStylesChanged();
7255 }
7256 }
7257
InsertSheetAt(size_t aIndex,StyleSheet & aSheet)7258 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
7259 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
7260
7261 if (aSheet.IsApplicable()) {
7262 AddStyleSheetToStyleSets(aSheet);
7263 }
7264 }
7265
StyleSheetApplicableStateChanged(StyleSheet & aSheet)7266 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
7267 const bool applicable = aSheet.IsApplicable();
7268 // If we're actually in the document style sheet list
7269 if (StyleOrderIndexOfSheet(aSheet) >= 0) {
7270 if (applicable) {
7271 AddStyleSheetToStyleSets(aSheet);
7272 } else {
7273 RemoveStyleSheetFromStyleSets(aSheet);
7274 }
7275 }
7276
7277 PostStyleSheetApplicableStateChangeEvent(aSheet);
7278
7279 if (!mSSApplicableStateNotificationPending) {
7280 MOZ_RELEASE_ASSERT(NS_IsMainThread());
7281 nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(
7282 "Document::NotifyStyleSheetApplicableStateChanged", this,
7283 &Document::NotifyStyleSheetApplicableStateChanged);
7284 mSSApplicableStateNotificationPending =
7285 NS_SUCCEEDED(Dispatch(TaskCategory::Other, notification.forget()));
7286 }
7287 }
7288
PostStyleSheetApplicableStateChangeEvent(StyleSheet & aSheet)7289 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
7290 if (!StyleSheetChangeEventsEnabled()) {
7291 return;
7292 }
7293
7294 StyleSheetApplicableStateChangeEventInit init;
7295 init.mBubbles = true;
7296 init.mCancelable = true;
7297 init.mStylesheet = &aSheet;
7298 init.mApplicable = aSheet.IsApplicable();
7299
7300 RefPtr<StyleSheetApplicableStateChangeEvent> event =
7301 StyleSheetApplicableStateChangeEvent::Constructor(
7302 this, u"StyleSheetApplicableStateChanged"_ns, init);
7303 event->SetTrusted(true);
7304 event->SetTarget(this);
7305 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7306 new AsyncEventDispatcher(this, event);
7307 asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
7308 asyncDispatcher->PostDOMEvent();
7309 }
7310
NotifyStyleSheetApplicableStateChanged()7311 void Document::NotifyStyleSheetApplicableStateChanged() {
7312 mSSApplicableStateNotificationPending = false;
7313 nsCOMPtr<nsIObserverService> observerService =
7314 mozilla::services::GetObserverService();
7315 if (observerService) {
7316 observerService->NotifyObservers(
7317 ToSupports(this), "style-sheet-applicable-state-changed", nullptr);
7318 }
7319 }
7320
FindSheet(const nsTArray<RefPtr<StyleSheet>> & aSheets,nsIURI * aSheetURI)7321 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
7322 nsIURI* aSheetURI) {
7323 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
7324 bool bEqual;
7325 nsIURI* uri = aSheets[i]->GetSheetURI();
7326
7327 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual)
7328 return i;
7329 }
7330
7331 return -1;
7332 }
7333
LoadAdditionalStyleSheet(additionalSheetType aType,nsIURI * aSheetURI)7334 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType,
7335 nsIURI* aSheetURI) {
7336 MOZ_ASSERT(aSheetURI, "null arg");
7337
7338 // Checking if we have loaded this one already.
7339 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0)
7340 return NS_ERROR_INVALID_ARG;
7341
7342 // Loading the sheet sync.
7343 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup());
7344
7345 css::SheetParsingMode parsingMode;
7346 switch (aType) {
7347 case Document::eAgentSheet:
7348 parsingMode = css::eAgentSheetFeatures;
7349 break;
7350
7351 case Document::eUserSheet:
7352 parsingMode = css::eUserSheetFeatures;
7353 break;
7354
7355 case Document::eAuthorSheet:
7356 parsingMode = css::eAuthorSheetFeatures;
7357 break;
7358
7359 default:
7360 MOZ_CRASH("impossible value for aType");
7361 }
7362
7363 auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
7364 css::Loader::UseSystemPrincipal::Yes);
7365 if (result.isErr()) {
7366 return result.unwrapErr();
7367 }
7368
7369 RefPtr<StyleSheet> sheet = result.unwrap();
7370
7371 sheet->SetAssociatedDocumentOrShadowRoot(this);
7372 MOZ_ASSERT(sheet->IsApplicable());
7373
7374 return AddAdditionalStyleSheet(aType, sheet);
7375 }
7376
AddAdditionalStyleSheet(additionalSheetType aType,StyleSheet * aSheet)7377 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType,
7378 StyleSheet* aSheet) {
7379 if (mAdditionalSheets[aType].Contains(aSheet)) {
7380 return NS_ERROR_INVALID_ARG;
7381 }
7382
7383 if (!aSheet->IsApplicable()) {
7384 return NS_ERROR_INVALID_ARG;
7385 }
7386
7387 mAdditionalSheets[aType].AppendElement(aSheet);
7388
7389 if (mStyleSetFilled) {
7390 mStyleSet->AppendStyleSheet(*aSheet);
7391 ApplicableStylesChanged();
7392 }
7393 return NS_OK;
7394 }
7395
RemoveAdditionalStyleSheet(additionalSheetType aType,nsIURI * aSheetURI)7396 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType,
7397 nsIURI* aSheetURI) {
7398 MOZ_ASSERT(aSheetURI);
7399
7400 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType];
7401
7402 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI);
7403 if (i >= 0) {
7404 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]);
7405 sheets.RemoveElementAt(i);
7406
7407 if (!mIsGoingAway) {
7408 MOZ_ASSERT(sheetRef->IsApplicable());
7409 if (mStyleSetFilled) {
7410 mStyleSet->RemoveStyleSheet(*sheetRef);
7411 ApplicableStylesChanged();
7412 }
7413 }
7414 sheetRef->ClearAssociatedDocumentOrShadowRoot();
7415 }
7416 }
7417
GetScopeObject() const7418 nsIGlobalObject* Document::GetScopeObject() const {
7419 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
7420 return scope;
7421 }
7422
CrossOriginIsolated() const7423 bool Document::CrossOriginIsolated() const {
7424 if (auto* bc = GetBrowsingContext()) {
7425 return bc->CrossOriginIsolated();
7426 }
7427
7428 // For a data document without a browsing context we check the
7429 // cross-origin-isolated state from its creator's inner window.
7430 if (mLoadedAsData) {
7431 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
7432 return window && window->GetBrowsingContext() &&
7433 window->GetBrowsingContext()->CrossOriginIsolated();
7434 }
7435
7436 return false;
7437 }
7438
GetDocGroupOrCreate()7439 DocGroup* Document::GetDocGroupOrCreate() {
7440 if (!mDocGroup) {
7441 nsAutoCString docGroupKey;
7442 nsresult rv = mozilla::dom::DocGroup::GetKey(
7443 NodePrincipal(), CrossOriginIsolated(), docGroupKey);
7444 if (NS_SUCCEEDED(rv) && mDocumentContainer) {
7445 BrowsingContextGroup* group = GetBrowsingContext()->Group();
7446 if (group) {
7447 mDocGroup = group->AddDocument(docGroupKey, this);
7448 }
7449 }
7450 }
7451 return mDocGroup;
7452 }
7453
SetScopeObject(nsIGlobalObject * aGlobal)7454 void Document::SetScopeObject(nsIGlobalObject* aGlobal) {
7455 mScopeObject = do_GetWeakReference(aGlobal);
7456 if (aGlobal) {
7457 mHasHadScriptHandlingObject = true;
7458
7459 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
7460 if (!window) {
7461 return;
7462 }
7463 BrowsingContextGroup* browsingContextGroup =
7464 window->GetBrowsingContextGroup();
7465
7466 // We should already have the principal, and now that we have been added
7467 // to a window, we should be able to join a DocGroup!
7468 nsAutoCString docGroupKey;
7469 nsresult rv = mozilla::dom::DocGroup::GetKey(
7470 NodePrincipal(), CrossOriginIsolated(), docGroupKey);
7471 if (mDocGroup) {
7472 if (NS_SUCCEEDED(rv)) {
7473 MOZ_RELEASE_ASSERT(mDocGroup->MatchesKey(docGroupKey));
7474 }
7475 MOZ_RELEASE_ASSERT(mDocGroup->GetBrowsingContextGroup() ==
7476 browsingContextGroup);
7477 } else {
7478 mDocGroup = browsingContextGroup->AddDocument(docGroupKey, this);
7479
7480 MOZ_ASSERT(mDocGroup);
7481 }
7482
7483 MOZ_ASSERT_IF(
7484 mNodeInfoManager->GetArenaAllocator(),
7485 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator());
7486 }
7487 }
7488
ContainsEMEContent()7489 bool Document::ContainsEMEContent() {
7490 nsPIDOMWindowInner* win = GetInnerWindow();
7491 // Note this case is different from checking just media elements in that
7492 // it covers when we've created MediaKeys but not associated them with a
7493 // media element.
7494 return win && win->HasActiveMediaKeysInstance();
7495 }
7496
ContainsMSEContent()7497 bool Document::ContainsMSEContent() {
7498 bool containsMSE = false;
7499
7500 auto check = [&containsMSE](nsISupports* aSupports) {
7501 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7502 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7503 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
7504 if (ms) {
7505 containsMSE = true;
7506 }
7507 }
7508 };
7509
7510 EnumerateActivityObservers(check);
7511 return containsMSE;
7512 }
7513
NotifyActivityChangedCallback(nsISupports * aSupports)7514 static void NotifyActivityChangedCallback(nsISupports* aSupports) {
7515 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports));
7516 if (auto mediaElem = HTMLMediaElement::FromNodeOrNull(content)) {
7517 mediaElem->NotifyOwnerDocumentActivityChanged();
7518 }
7519 nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(
7520 do_QueryInterface(aSupports));
7521 if (objectLoadingContent) {
7522 nsObjectLoadingContent* olc =
7523 static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
7524 olc->NotifyOwnerDocumentActivityChanged();
7525 }
7526 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity(
7527 do_QueryInterface(aSupports));
7528 if (objectDocumentActivity) {
7529 objectDocumentActivity->NotifyOwnerDocumentActivityChanged();
7530 } else {
7531 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent(
7532 do_QueryInterface(aSupports));
7533 if (imageLoadingContent) {
7534 auto ilc = static_cast<nsImageLoadingContent*>(imageLoadingContent.get());
7535 ilc->NotifyOwnerDocumentActivityChanged();
7536 }
7537 }
7538 }
7539
NotifyActivityChanged()7540 void Document::NotifyActivityChanged() {
7541 EnumerateActivityObservers(NotifyActivityChangedCallback);
7542 }
7543
IsTopLevelWindowInactive() const7544 bool Document::IsTopLevelWindowInactive() const {
7545 if (BrowsingContext* bc = GetBrowsingContext()) {
7546 return !bc->GetIsActiveBrowserWindow();
7547 }
7548
7549 return false;
7550 }
7551
SetContainer(nsDocShell * aContainer)7552 void Document::SetContainer(nsDocShell* aContainer) {
7553 if (aContainer) {
7554 mDocumentContainer = aContainer;
7555 } else {
7556 mDocumentContainer = WeakPtr<nsDocShell>();
7557 }
7558
7559 mInChromeDocShell =
7560 aContainer && aContainer->GetBrowsingContext()->IsChrome();
7561
7562 NotifyActivityChanged();
7563
7564 // IsTopLevelWindowInactive depends on the docshell, so
7565 // update the cached value now that it's available.
7566 UpdateDocumentStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE, false);
7567 if (!aContainer) {
7568 return;
7569 }
7570
7571 BrowsingContext* context = aContainer->GetBrowsingContext();
7572 if (context && context->IsContent()) {
7573 SetIsTopLevelContentDocument(context->IsTopContent());
7574 SetIsContentDocument(true);
7575 } else {
7576 SetIsTopLevelContentDocument(false);
7577 SetIsContentDocument(false);
7578 }
7579 }
7580
GetContainer() const7581 nsISupports* Document::GetContainer() const {
7582 return static_cast<nsIDocShell*>(mDocumentContainer);
7583 }
7584
SetScriptGlobalObject(nsIScriptGlobalObject * aScriptGlobalObject)7585 void Document::SetScriptGlobalObject(
7586 nsIScriptGlobalObject* aScriptGlobalObject) {
7587 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
7588 mAnimationController->IsPausedByType(
7589 SMILTimeContainer::PAUSE_PAGEHIDE |
7590 SMILTimeContainer::PAUSE_BEGIN),
7591 "Clearing window pointer while animations are unpaused");
7592
7593 if (mScriptGlobalObject && !aScriptGlobalObject) {
7594 // We're detaching from the window. We need to grab a pointer to
7595 // our layout history state now.
7596 mLayoutHistoryState = GetLayoutHistoryState();
7597
7598 // Also make sure to remove our onload blocker now if we haven't done it yet
7599 if (mOnloadBlockCount != 0) {
7600 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
7601 if (loadGroup) {
7602 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
7603 }
7604 }
7605
7606 if (GetController().isSome()) {
7607 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) {
7608 loader->ClearCacheForControlledDocument(this);
7609 }
7610
7611 // We may become controlled again if this document comes back out
7612 // of bfcache. Clear our state to allow that to happen. Only
7613 // clear this flag if we are actually controlled, though, so pages
7614 // that were force reloaded don't become controlled when they
7615 // come out of bfcache.
7616 mMaybeServiceWorkerControlled = false;
7617 }
7618
7619 if (GetWindowContext()) {
7620 // The document is about to lose its window, so this is a good time to
7621 // send our page use counters, while we still have access to our
7622 // WindowContext.
7623 //
7624 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which
7625 // catches some cases of documents losing their window that don't
7626 // get in here.)
7627 SendPageUseCounters();
7628 }
7629 }
7630
7631 // BlockOnload() might be called before mScriptGlobalObject is set.
7632 // We may need to add the blocker once mScriptGlobalObject is set.
7633 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
7634
7635 mScriptGlobalObject = aScriptGlobalObject;
7636
7637 if (needOnloadBlocker) {
7638 EnsureOnloadBlocker();
7639 }
7640
7641 UpdateFrameRequestCallbackSchedulingState();
7642
7643 if (aScriptGlobalObject) {
7644 // Go back to using the docshell for the layout history state
7645 mLayoutHistoryState = nullptr;
7646 SetScopeObject(aScriptGlobalObject);
7647 mHasHadDefaultView = true;
7648
7649 if (mAllowDNSPrefetch) {
7650 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
7651 if (docShell) {
7652 #ifdef DEBUG
7653 nsCOMPtr<nsIWebNavigation> webNav =
7654 do_GetInterface(aScriptGlobalObject);
7655 NS_ASSERTION(SameCOMIdentity(webNav, docShell),
7656 "Unexpected container or script global?");
7657 #endif
7658 bool allowDNSPrefetch;
7659 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
7660 mAllowDNSPrefetch = allowDNSPrefetch;
7661 }
7662 }
7663
7664 // If we are set in a window that is already focused we should remember this
7665 // as the time the document gained focus.
7666 if (HasFocus(IgnoreErrors())) {
7667 SetLastFocusTime(TimeStamp::Now());
7668 }
7669 }
7670
7671 // Remember the pointer to our window (or lack there of), to avoid
7672 // having to QI every time it's asked for.
7673 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject);
7674 mWindow = window;
7675
7676 // Now that we know what our window is, we can flush the CSP errors to the
7677 // Web Console. We are flushing all messages that occurred and were stored in
7678 // the queue prior to this point.
7679 if (mCSP) {
7680 static_cast<nsCSPContext*>(mCSP.get())->flushConsoleMessages();
7681 }
7682
7683 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
7684 do_QueryInterface(GetChannel());
7685 if (internalChannel) {
7686 nsCOMArray<nsISecurityConsoleMessage> messages;
7687 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages);
7688 MOZ_ASSERT(NS_SUCCEEDED(rv));
7689 SendToConsole(messages);
7690 }
7691
7692 // Set our visibility state, but do not fire the event. This is correct
7693 // because either we're coming out of bfcache (in which case IsVisible() will
7694 // still test false at this point and no state change will happen) or we're
7695 // doing the initial document load and don't want to fire the event for this
7696 // change.
7697 //
7698 // When the visibility is changed, notify it to observers.
7699 // Some observers need the notification, for example HTMLMediaElement uses
7700 // it to update internal media resource allocation.
7701 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder
7702 // creation are already done before Document::SetScriptGlobalObject() call.
7703 // MediaDecoder decides whether starting decoding is decided based on
7704 // document's visibility. When the MediaDecoder is created,
7705 // Document::SetScriptGlobalObject() is not yet called and document is
7706 // hidden state. Therefore the MediaDecoder decides that decoding is
7707 // not yet necessary. But soon after Document::SetScriptGlobalObject()
7708 // call, the document becomes not hidden. At the time, MediaDecoder needs
7709 // to know it and needs to start updating decoding.
7710 UpdateVisibilityState(DispatchVisibilityChange::No);
7711
7712 // The global in the template contents owner document should be the same.
7713 if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
7714 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
7715 }
7716
7717 if (!mMaybeServiceWorkerControlled && mDocumentContainer &&
7718 mScriptGlobalObject && GetChannel()) {
7719 // If we are shift-reloaded, don't associate with a ServiceWorker.
7720 if (mDocumentContainer->IsForceReloading()) {
7721 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
7722 return;
7723 }
7724
7725 mMaybeServiceWorkerControlled = true;
7726 }
7727 }
7728
GetScriptHandlingObjectInternal() const7729 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const {
7730 MOZ_ASSERT(!mScriptGlobalObject,
7731 "Do not call this when mScriptGlobalObject is set!");
7732 if (mHasHadDefaultView) {
7733 return nullptr;
7734 }
7735
7736 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
7737 do_QueryReferent(mScopeObject);
7738 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject);
7739 if (win) {
7740 nsPIDOMWindowOuter* outer = win->GetOuterWindow();
7741 if (!outer || outer->GetCurrentInnerWindow() != win) {
7742 NS_WARNING("Wrong inner/outer window combination!");
7743 return nullptr;
7744 }
7745 }
7746 return scriptHandlingObject;
7747 }
SetScriptHandlingObject(nsIScriptGlobalObject * aScriptObject)7748 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) {
7749 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject,
7750 "Wrong script object!");
7751 if (aScriptObject) {
7752 SetScopeObject(aScriptObject);
7753 mHasHadDefaultView = false;
7754 }
7755 }
7756
GetWindowInternal() const7757 nsPIDOMWindowOuter* Document::GetWindowInternal() const {
7758 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!");
7759 // Let's use mScriptGlobalObject. Even if the document is already removed from
7760 // the docshell, the outer window might be still obtainable from the it.
7761 nsCOMPtr<nsPIDOMWindowOuter> win;
7762 if (mRemovedFromDocShell) {
7763 // The docshell returns the outer window we are done.
7764 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer);
7765 if (kungFuDeathGrip) {
7766 win = kungFuDeathGrip->GetWindow();
7767 }
7768 } else {
7769 if (nsCOMPtr<nsPIDOMWindowInner> inner =
7770 do_QueryInterface(mScriptGlobalObject)) {
7771 // mScriptGlobalObject is always the inner window, let's get the outer.
7772 win = inner->GetOuterWindow();
7773 }
7774 }
7775
7776 return win;
7777 }
7778
InternalAllowXULXBL()7779 bool Document::InternalAllowXULXBL() {
7780 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) {
7781 mAllowXULXBL = eTriTrue;
7782 return true;
7783 }
7784
7785 mAllowXULXBL = eTriFalse;
7786 return false;
7787 }
7788
7789 // Note: We don't hold a reference to the document observer; we assume
7790 // that it has a live reference to the document.
AddObserver(nsIDocumentObserver * aObserver)7791 void Document::AddObserver(nsIDocumentObserver* aObserver) {
7792 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
7793 "Observer already in the list");
7794 mObservers.AppendElement(aObserver);
7795 AddMutationObserver(aObserver);
7796 }
7797
RemoveObserver(nsIDocumentObserver * aObserver)7798 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) {
7799 // If we're in the process of destroying the document (and we're
7800 // informing the observers of the destruction), don't remove the
7801 // observers from the list. This is not a big deal, since we
7802 // don't hold a live reference to the observers.
7803 if (!mInDestructor) {
7804 RemoveMutationObserver(aObserver);
7805 return mObservers.RemoveElement(aObserver);
7806 }
7807
7808 return mObservers.Contains(aObserver);
7809 }
7810
BeginUpdate()7811 void Document::BeginUpdate() {
7812 ++mUpdateNestLevel;
7813 nsContentUtils::AddScriptBlocker();
7814 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this));
7815 }
7816
EndUpdate()7817 void Document::EndUpdate() {
7818 const bool reset = !mPendingMaybeEditingStateChanged;
7819 mPendingMaybeEditingStateChanged = true;
7820
7821 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this));
7822
7823 --mUpdateNestLevel;
7824
7825 nsContentUtils::RemoveScriptBlocker();
7826
7827 if (mXULBroadcastManager) {
7828 mXULBroadcastManager->MaybeBroadcast();
7829 }
7830
7831 if (reset) {
7832 mPendingMaybeEditingStateChanged = false;
7833 }
7834 MaybeEditingStateChanged();
7835 }
7836
BeginLoad()7837 void Document::BeginLoad() {
7838 if (IsEditingOn()) {
7839 // Reset() blows away all event listeners in the document, and our
7840 // editor relies heavily on those. Midas is turned on, to make it
7841 // work, re-initialize it to give it a chance to add its event
7842 // listeners again.
7843
7844 TurnEditingOff();
7845 EditingStateChanged();
7846 }
7847
7848 MOZ_ASSERT(!mDidCallBeginLoad);
7849 mDidCallBeginLoad = true;
7850
7851 // Block onload here to prevent having to deal with blocking and
7852 // unblocking it while we know the document is loading.
7853 BlockOnload();
7854 mDidFireDOMContentLoaded = false;
7855 BlockDOMContentLoaded();
7856
7857 if (mScriptLoader) {
7858 mScriptLoader->BeginDeferringScripts();
7859 }
7860
7861 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this));
7862 }
7863
MozSetImageElement(const nsAString & aImageElementId,Element * aElement)7864 void Document::MozSetImageElement(const nsAString& aImageElementId,
7865 Element* aElement) {
7866 if (aImageElementId.IsEmpty()) return;
7867
7868 // Hold a script blocker while calling SetImageElement since that can call
7869 // out to id-observers
7870 nsAutoScriptBlocker scriptBlocker;
7871
7872 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId);
7873 if (entry) {
7874 entry->SetImageElement(aElement);
7875 if (entry->IsEmpty()) {
7876 mIdentifierMap.RemoveEntry(entry);
7877 }
7878 }
7879 }
7880
DispatchContentLoadedEvents()7881 void Document::DispatchContentLoadedEvents() {
7882 // If you add early returns from this method, make sure you're
7883 // calling UnblockOnload properly.
7884
7885 // Unpin references to preloaded images
7886 mPreloadingImages.Clear();
7887
7888 // DOM manipulation after content loaded should not care if the element
7889 // came from the preloader.
7890 mPreloadedPreconnects.Clear();
7891
7892 if (mTiming) {
7893 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI());
7894 }
7895
7896 // Dispatch observer notification to notify observers document is interactive.
7897 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
7898 if (os) {
7899 nsIPrincipal* principal = NodePrincipal();
7900 os->NotifyObservers(ToSupports(this),
7901 principal->IsSystemPrincipal()
7902 ? "chrome-document-interactive"
7903 : "content-document-interactive",
7904 nullptr);
7905 }
7906
7907 // Fire a DOM event notifying listeners that this document has been
7908 // loaded (excluding images and other loads initiated by this
7909 // document).
7910 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
7911 u"DOMContentLoaded"_ns, CanBubble::eYes,
7912 Cancelable::eNo);
7913
7914 if (auto* const window = GetInnerWindow()) {
7915 const RefPtr<ServiceWorkerContainer> serviceWorker =
7916 window->Navigator()->ServiceWorker();
7917
7918 // This could cause queued messages from a service worker to get
7919 // dispatched on serviceWorker.
7920 serviceWorker->StartMessages();
7921 }
7922
7923 if (MayStartLayout()) {
7924 MaybeResolveReadyForIdle();
7925 }
7926
7927 RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
7928 nsIDocShell* docShell = this->GetDocShell();
7929
7930 if (timelines && timelines->HasConsumer(docShell)) {
7931 timelines->AddMarkerForDocShell(
7932 docShell,
7933 MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
7934 }
7935
7936 if (mTiming) {
7937 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI());
7938 }
7939
7940 // If this document is a [i]frame, fire a DOMFrameContentLoaded
7941 // event on all parent documents notifying that the HTML (excluding
7942 // other external files such as images and stylesheets) in a frame
7943 // has finished loading.
7944
7945 // target_frame is the [i]frame element that will be used as the
7946 // target for the event. It's the [i]frame whose content is done
7947 // loading.
7948 nsCOMPtr<Element> target_frame = GetEmbedderElement();
7949
7950 if (target_frame && target_frame->IsInComposedDoc()) {
7951 nsCOMPtr<Document> parent = target_frame->OwnerDoc();
7952 while (parent) {
7953 RefPtr<Event> event;
7954 if (parent) {
7955 IgnoredErrorResult ignored;
7956 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored);
7957 }
7958
7959 if (event) {
7960 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true);
7961
7962 event->SetTarget(target_frame);
7963 event->SetTrusted(true);
7964
7965 // To dispatch this event we must manually call
7966 // EventDispatcher::Dispatch() on the ancestor document since the
7967 // target is not in the same document, so the event would never reach
7968 // the ancestor document if we used the normal event
7969 // dispatching code.
7970
7971 WidgetEvent* innerEvent = event->WidgetEventPtr();
7972 if (innerEvent) {
7973 nsEventStatus status = nsEventStatus_eIgnore;
7974
7975 if (RefPtr<nsPresContext> context = parent->GetPresContext()) {
7976 // TODO: Bug 1506441
7977 EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(parent)),
7978 context, innerEvent, event, &status);
7979 }
7980 }
7981 }
7982
7983 parent = parent->GetInProcessParentDocument();
7984 }
7985 }
7986
7987 // If the document has a manifest attribute, fire a MozApplicationManifest
7988 // event.
7989 Element* root = GetRootElement();
7990 if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::manifest)) {
7991 nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
7992 u"MozApplicationManifest"_ns,
7993 CanBubble::eYes, Cancelable::eYes);
7994 }
7995
7996 nsPIDOMWindowInner* inner = GetInnerWindow();
7997 if (inner) {
7998 inner->NoteDOMContentLoaded();
7999 }
8000
8001 // TODO
8002 if (mMaybeServiceWorkerControlled) {
8003 using mozilla::dom::ServiceWorkerManager;
8004 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
8005 if (swm) {
8006 Maybe<ClientInfo> clientInfo = GetClientInfo();
8007 if (clientInfo.isSome()) {
8008 swm->MaybeCheckNavigationUpdate(clientInfo.ref());
8009 }
8010 }
8011 }
8012
8013 if (mSetCompleteAfterDOMContentLoaded) {
8014 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE);
8015 mSetCompleteAfterDOMContentLoaded = false;
8016 }
8017
8018 UnblockOnload(true);
8019 }
8020
EndLoad()8021 void Document::EndLoad() {
8022 bool turnOnEditing =
8023 mParser && (IsInDesignMode() || mContentEditableCount > 0);
8024
8025 #if defined(DEBUG)
8026 // only assert if nothing stopped the load on purpose
8027 if (!mParserAborted) {
8028 nsContentSecurityUtils::AssertAboutPageHasCSP(this);
8029 }
8030 #endif
8031
8032 // EndLoad may have been called without a matching call to BeginLoad, in the
8033 // case of a failed parse (for example, due to timeout). In such a case, we
8034 // still want to execute part of this code to do appropriate cleanup, but we
8035 // gate part of it because it is intended to match 1-for-1 with calls to
8036 // BeginLoad. We have an explicit flag bit for this purpose, since it's
8037 // complicated and error prone to derive this condition from other related
8038 // flags that can be manipulated outside of a BeginLoad/EndLoad pair.
8039
8040 // Part 1: Code that always executes to cleanup end of parsing, whether
8041 // that parsing was successful or not.
8042
8043 // Drop the ref to our parser, if any, but keep hold of the sink so that we
8044 // can flush it from FlushPendingNotifications as needed. We might have to
8045 // do that to get a StartLayout() to happen.
8046 if (mParser) {
8047 mWeakSink = do_GetWeakReference(mParser->GetContentSink());
8048 mParser = nullptr;
8049 }
8050
8051 // Update the attributes on the PerformanceNavigationTiming before notifying
8052 // the onload observers.
8053 if (nsPIDOMWindowInner* window = GetInnerWindow()) {
8054 if (RefPtr<Performance> performance = window->GetPerformance()) {
8055 performance->UpdateNavigationTimingEntry();
8056 }
8057 }
8058
8059 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
8060
8061 // Part 2: Code that only executes when this EndLoad matches a BeginLoad.
8062
8063 if (!mDidCallBeginLoad) {
8064 return;
8065 }
8066 mDidCallBeginLoad = false;
8067
8068 UnblockDOMContentLoaded();
8069
8070 if (turnOnEditing) {
8071 EditingStateChanged();
8072 }
8073
8074 if (!GetWindow()) {
8075 // This is a document that's not in a window. For example, this could be an
8076 // XMLHttpRequest responseXML document, or a document created via DOMParser
8077 // or DOMImplementation. We don't reach this code normally for such
8078 // documents (which is not obviously correct), but can reach it via
8079 // document.open()/document.close().
8080 //
8081 // Such documents don't fire load events, but per spec should set their
8082 // readyState to "complete" when parsing and all loading of subresources is
8083 // done. Parsing is done now, and documents not in a window don't load
8084 // subresources, so just go ahead and mark ourselves as complete.
8085 SetReadyStateInternal(Document::READYSTATE_COMPLETE,
8086 /* updateTimingInformation = */ false);
8087
8088 // Reset mSkipLoadEventAfterClose just in case.
8089 mSkipLoadEventAfterClose = false;
8090 }
8091 }
8092
UnblockDOMContentLoaded()8093 void Document::UnblockDOMContentLoaded() {
8094 MOZ_ASSERT(mBlockDOMContentLoaded);
8095 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
8096 return;
8097 }
8098
8099 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
8100 ("DOCUMENT %p UnblockDOMContentLoaded", this));
8101
8102 mDidFireDOMContentLoaded = true;
8103 if (PresShell* presShell = GetPresShell()) {
8104 presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
8105 }
8106
8107 MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
8108 if (!mSynchronousDOMContentLoaded) {
8109 MOZ_RELEASE_ASSERT(NS_IsMainThread());
8110 nsCOMPtr<nsIRunnable> ev =
8111 NewRunnableMethod("Document::DispatchContentLoadedEvents", this,
8112 &Document::DispatchContentLoadedEvents);
8113 Dispatch(TaskCategory::Other, ev.forget());
8114 } else {
8115 DispatchContentLoadedEvents();
8116 }
8117 }
8118
ContentStateChanged(nsIContent * aContent,EventStates aStateMask)8119 void Document::ContentStateChanged(nsIContent* aContent,
8120 EventStates aStateMask) {
8121 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
8122 "Someone forgot a scriptblocker");
8123 NS_DOCUMENT_NOTIFY_OBSERVERS(ContentStateChanged,
8124 (this, aContent, aStateMask));
8125 }
8126
RuleChanged(StyleSheet & aSheet,css::Rule *,StyleRuleChangeKind)8127 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*,
8128 StyleRuleChangeKind) {
8129 if (aSheet.IsApplicable()) {
8130 ApplicableStylesChanged();
8131 }
8132 }
8133
RuleAdded(StyleSheet & aSheet,css::Rule & aRule)8134 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
8135 if (aRule.IsIncompleteImportRule()) {
8136 return;
8137 }
8138
8139 if (aSheet.IsApplicable()) {
8140 ApplicableStylesChanged();
8141 }
8142 }
8143
ImportRuleLoaded(dom::CSSImportRule & aRule,StyleSheet & aSheet)8144 void Document::ImportRuleLoaded(dom::CSSImportRule& aRule, StyleSheet& aSheet) {
8145 if (aSheet.IsApplicable()) {
8146 ApplicableStylesChanged();
8147 }
8148 }
8149
RuleRemoved(StyleSheet & aSheet,css::Rule & aRule)8150 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
8151 if (aSheet.IsApplicable()) {
8152 ApplicableStylesChanged();
8153 }
8154 }
8155
GetCustomContentContainer(PresShell * aPresShell)8156 static Element* GetCustomContentContainer(PresShell* aPresShell) {
8157 if (!aPresShell || !aPresShell->GetCanvasFrame()) {
8158 return nullptr;
8159 }
8160
8161 return aPresShell->GetCanvasFrame()->GetCustomContentContainer();
8162 }
8163
InsertAnonymousContent(Element & aElement,bool aForce,ErrorResult & aRv)8164 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent(
8165 Element& aElement, bool aForce, ErrorResult& aRv) {
8166 // Clone the node to avoid returning a direct reference.
8167 nsCOMPtr<nsINode> clone = aElement.CloneNode(true, aRv);
8168 if (aRv.Failed()) {
8169 return nullptr;
8170 }
8171
8172 PresShell* shell = GetPresShell();
8173 if (aForce && !GetCustomContentContainer(shell)) {
8174 FlushPendingNotifications(FlushType::Layout);
8175 shell = GetPresShell();
8176 }
8177
8178 nsAutoScriptBlocker scriptBlocker;
8179
8180 auto anonContent =
8181 MakeRefPtr<AnonymousContent>(clone.forget().downcast<Element>());
8182
8183 mAnonymousContents.AppendElement(anonContent);
8184
8185 if (Element* container = GetCustomContentContainer(shell)) {
8186 container->AppendChildTo(&anonContent->ContentNode(), true, IgnoreErrors());
8187 shell->GetCanvasFrame()->ShowCustomContentContainer();
8188 }
8189
8190 return anonContent.forget();
8191 }
8192
RemoveAnonContentFromCanvas(AnonymousContent & aAnonContent,PresShell * aPresShell)8193 static void RemoveAnonContentFromCanvas(AnonymousContent& aAnonContent,
8194 PresShell* aPresShell) {
8195 RefPtr<Element> container = GetCustomContentContainer(aPresShell);
8196 if (!container) {
8197 return;
8198 }
8199 container->RemoveChild(aAnonContent.ContentNode(), IgnoreErrors());
8200 }
8201
RemoveAnonymousContent(AnonymousContent & aContent,ErrorResult & aRv)8202 void Document::RemoveAnonymousContent(AnonymousContent& aContent,
8203 ErrorResult& aRv) {
8204 nsAutoScriptBlocker scriptBlocker;
8205
8206 auto index = mAnonymousContents.IndexOf(&aContent);
8207 if (index == mAnonymousContents.NoIndex) {
8208 return;
8209 }
8210
8211 mAnonymousContents.RemoveElementAt(index);
8212 RemoveAnonContentFromCanvas(aContent, GetPresShell());
8213
8214 if (mAnonymousContents.IsEmpty() &&
8215 GetCustomContentContainer(GetPresShell())) {
8216 GetPresShell()->GetCanvasFrame()->HideCustomContentContainer();
8217 }
8218 }
8219
GetAnonRootIfInAnonymousContentContainer(nsINode * aNode) const8220 Element* Document::GetAnonRootIfInAnonymousContentContainer(
8221 nsINode* aNode) const {
8222 if (!aNode->IsInNativeAnonymousSubtree()) {
8223 return nullptr;
8224 }
8225
8226 PresShell* presShell = GetPresShell();
8227 if (!presShell || !presShell->GetCanvasFrame()) {
8228 return nullptr;
8229 }
8230
8231 nsAutoScriptBlocker scriptBlocker;
8232 nsCOMPtr<Element> customContainer =
8233 presShell->GetCanvasFrame()->GetCustomContentContainer();
8234 if (!customContainer) {
8235 return nullptr;
8236 }
8237
8238 // An arbitrary number of elements can be inserted as children of the custom
8239 // container frame. We want the one that was added that contains aNode, so
8240 // we need to keep track of the last child separately using |child| here.
8241 nsINode* child = aNode;
8242 nsINode* parent = aNode->GetParentNode();
8243 while (parent && parent->IsInNativeAnonymousSubtree()) {
8244 if (parent == customContainer) {
8245 return Element::FromNode(child);
8246 }
8247 child = parent;
8248 parent = child->GetParentNode();
8249 }
8250 return nullptr;
8251 }
8252
GetClientInfo() const8253 Maybe<ClientInfo> Document::GetClientInfo() const {
8254 if (const Document* orig = GetOriginalDocument()) {
8255 if (Maybe<ClientInfo> info = orig->GetClientInfo()) {
8256 return info;
8257 }
8258 }
8259
8260 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8261 return inner->GetClientInfo();
8262 }
8263
8264 return Maybe<ClientInfo>();
8265 }
8266
GetClientState() const8267 Maybe<ClientState> Document::GetClientState() const {
8268 if (const Document* orig = GetOriginalDocument()) {
8269 if (Maybe<ClientState> state = orig->GetClientState()) {
8270 return state;
8271 }
8272 }
8273
8274 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8275 return inner->GetClientState();
8276 }
8277
8278 return Maybe<ClientState>();
8279 }
8280
GetController() const8281 Maybe<ServiceWorkerDescriptor> Document::GetController() const {
8282 if (const Document* orig = GetOriginalDocument()) {
8283 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) {
8284 return controller;
8285 }
8286 }
8287
8288 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
8289 return inner->GetController();
8290 }
8291
8292 return Maybe<ServiceWorkerDescriptor>();
8293 }
8294
8295 //
8296 // Document interface
8297 //
GetDoctype() const8298 DocumentType* Document::GetDoctype() const {
8299 for (nsIContent* child = GetFirstChild(); child;
8300 child = child->GetNextSibling()) {
8301 if (child->NodeType() == DOCUMENT_TYPE_NODE) {
8302 return static_cast<DocumentType*>(child);
8303 }
8304 }
8305 return nullptr;
8306 }
8307
GetImplementation(ErrorResult & rv)8308 DOMImplementation* Document::GetImplementation(ErrorResult& rv) {
8309 if (!mDOMImplementation) {
8310 nsCOMPtr<nsIURI> uri;
8311 NS_NewURI(getter_AddRefs(uri), "about:blank");
8312 if (!uri) {
8313 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
8314 return nullptr;
8315 }
8316 bool hasHadScriptObject = true;
8317 nsIScriptGlobalObject* scriptObject =
8318 GetScriptHandlingObject(hasHadScriptObject);
8319 if (!scriptObject && hasHadScriptObject) {
8320 rv.Throw(NS_ERROR_UNEXPECTED);
8321 return nullptr;
8322 }
8323 mDOMImplementation = new DOMImplementation(
8324 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri);
8325 }
8326
8327 return mDOMImplementation;
8328 }
8329
IsLowercaseASCII(const nsAString & aValue)8330 bool IsLowercaseASCII(const nsAString& aValue) {
8331 int32_t len = aValue.Length();
8332 for (int32_t i = 0; i < len; ++i) {
8333 char16_t c = aValue[i];
8334 if (!(0x0061 <= (c) && ((c) <= 0x007a))) {
8335 return false;
8336 }
8337 }
8338 return true;
8339 }
8340
CreateElement(const nsAString & aTagName,const ElementCreationOptionsOrString & aOptions,ErrorResult & rv)8341 already_AddRefed<Element> Document::CreateElement(
8342 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8343 ErrorResult& rv) {
8344 rv = nsContentUtils::CheckQName(aTagName, false);
8345 if (rv.Failed()) {
8346 return nullptr;
8347 }
8348
8349 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
8350 nsAutoString lcTagName;
8351 if (needsLowercase) {
8352 nsContentUtils::ASCIIToLower(aTagName, lcTagName);
8353 }
8354
8355 const nsString* is = nullptr;
8356 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo;
8357 if (aOptions.IsElementCreationOptions()) {
8358 const ElementCreationOptions& options =
8359 aOptions.GetAsElementCreationOptions();
8360
8361 if (options.mIs.WasPassed()) {
8362 is = &options.mIs.Value();
8363 }
8364
8365 // Check 'pseudo' and throw an exception if it's not one allowed
8366 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC.
8367 if (options.mPseudo.WasPassed()) {
8368 Maybe<PseudoStyleType> type =
8369 nsCSSPseudoElements::GetPseudoType(options.mPseudo.Value());
8370 if (!type || *type == PseudoStyleType::NotPseudo ||
8371 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(*type)) {
8372 rv.ThrowNotSupportedError("Invalid pseudo-element");
8373 return nullptr;
8374 }
8375 pseudoType = *type;
8376 }
8377 }
8378
8379 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName,
8380 nullptr, mDefaultElementType, is);
8381
8382 if (pseudoType != PseudoStyleType::NotPseudo) {
8383 elem->SetPseudoElementType(pseudoType);
8384 }
8385
8386 return elem.forget();
8387 }
8388
CreateElementNS(const nsAString & aNamespaceURI,const nsAString & aQualifiedName,const ElementCreationOptionsOrString & aOptions,ErrorResult & rv)8389 already_AddRefed<Element> Document::CreateElementNS(
8390 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8391 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) {
8392 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8393 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8394 mNodeInfoManager, ELEMENT_NODE,
8395 getter_AddRefs(nodeInfo));
8396 if (rv.Failed()) {
8397 return nullptr;
8398 }
8399
8400 const nsString* is = nullptr;
8401 if (aOptions.IsElementCreationOptions()) {
8402 const ElementCreationOptions& options =
8403 aOptions.GetAsElementCreationOptions();
8404 if (options.mIs.WasPassed()) {
8405 is = &options.mIs.Value();
8406 }
8407 }
8408
8409 nsCOMPtr<Element> element;
8410 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
8411 NOT_FROM_PARSER, is);
8412 if (rv.Failed()) {
8413 return nullptr;
8414 }
8415
8416 return element.forget();
8417 }
8418
CreateXULElement(const nsAString & aTagName,const ElementCreationOptionsOrString & aOptions,ErrorResult & aRv)8419 already_AddRefed<Element> Document::CreateXULElement(
8420 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions,
8421 ErrorResult& aRv) {
8422 aRv = nsContentUtils::CheckQName(aTagName, false);
8423 if (aRv.Failed()) {
8424 return nullptr;
8425 }
8426
8427 const nsString* is = nullptr;
8428 if (aOptions.IsElementCreationOptions()) {
8429 const ElementCreationOptions& options =
8430 aOptions.GetAsElementCreationOptions();
8431 if (options.mIs.WasPassed()) {
8432 is = &options.mIs.Value();
8433 }
8434 }
8435
8436 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is);
8437 if (!elem) {
8438 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
8439 return nullptr;
8440 }
8441 return elem.forget();
8442 }
8443
CreateEmptyTextNode() const8444 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const {
8445 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8446 return text.forget();
8447 }
8448
CreateTextNode(const nsAString & aData) const8449 already_AddRefed<nsTextNode> Document::CreateTextNode(
8450 const nsAString& aData) const {
8451 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
8452 // Don't notify; this node is still being created.
8453 text->SetText(aData, false);
8454 return text.forget();
8455 }
8456
CreateDocumentFragment() const8457 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const {
8458 RefPtr<DocumentFragment> frag =
8459 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager);
8460 return frag.forget();
8461 }
8462
8463 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
CreateComment(const nsAString & aData) const8464 already_AddRefed<dom::Comment> Document::CreateComment(
8465 const nsAString& aData) const {
8466 RefPtr<dom::Comment> comment =
8467 new (mNodeInfoManager) dom::Comment(mNodeInfoManager);
8468
8469 // Don't notify; this node is still being created.
8470 comment->SetText(aData, false);
8471 return comment.forget();
8472 }
8473
CreateCDATASection(const nsAString & aData,ErrorResult & rv)8474 already_AddRefed<CDATASection> Document::CreateCDATASection(
8475 const nsAString& aData, ErrorResult& rv) {
8476 if (IsHTMLDocument()) {
8477 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8478 return nullptr;
8479 }
8480
8481 if (FindInReadable(u"]]>"_ns, aData)) {
8482 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8483 return nullptr;
8484 }
8485
8486 RefPtr<CDATASection> cdata =
8487 new (mNodeInfoManager) CDATASection(mNodeInfoManager);
8488
8489 // Don't notify; this node is still being created.
8490 cdata->SetText(aData, false);
8491
8492 return cdata.forget();
8493 }
8494
CreateProcessingInstruction(const nsAString & aTarget,const nsAString & aData,ErrorResult & rv) const8495 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction(
8496 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const {
8497 nsresult res = nsContentUtils::CheckQName(aTarget, false);
8498 if (NS_FAILED(res)) {
8499 rv.Throw(res);
8500 return nullptr;
8501 }
8502
8503 if (FindInReadable(u"?>"_ns, aData)) {
8504 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
8505 return nullptr;
8506 }
8507
8508 RefPtr<ProcessingInstruction> pi =
8509 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
8510
8511 return pi.forget();
8512 }
8513
CreateAttribute(const nsAString & aName,ErrorResult & rv)8514 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName,
8515 ErrorResult& rv) {
8516 if (!mNodeInfoManager) {
8517 rv.Throw(NS_ERROR_NOT_INITIALIZED);
8518 return nullptr;
8519 }
8520
8521 nsresult res = nsContentUtils::CheckQName(aName, false);
8522 if (NS_FAILED(res)) {
8523 rv.Throw(res);
8524 return nullptr;
8525 }
8526
8527 nsAutoString name;
8528 if (IsHTMLDocument()) {
8529 nsContentUtils::ASCIIToLower(aName, name);
8530 } else {
8531 name = aName;
8532 }
8533
8534 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8535 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None,
8536 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo));
8537 if (NS_FAILED(res)) {
8538 rv.Throw(res);
8539 return nullptr;
8540 }
8541
8542 RefPtr<Attr> attribute =
8543 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8544 return attribute.forget();
8545 }
8546
CreateAttributeNS(const nsAString & aNamespaceURI,const nsAString & aQualifiedName,ErrorResult & rv)8547 already_AddRefed<Attr> Document::CreateAttributeNS(
8548 const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
8549 ErrorResult& rv) {
8550 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
8551 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
8552 mNodeInfoManager, ATTRIBUTE_NODE,
8553 getter_AddRefs(nodeInfo));
8554 if (rv.Failed()) {
8555 return nullptr;
8556 }
8557
8558 RefPtr<Attr> attribute =
8559 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns);
8560 return attribute.forget();
8561 }
8562
ResolveScheduledSVGPresAttrs()8563 void Document::ResolveScheduledSVGPresAttrs() {
8564 for (SVGElement* svg : mLazySVGPresElements) {
8565 svg->UpdateContentDeclarationBlock();
8566 }
8567 mLazySVGPresElements.Clear();
8568 }
8569
BlockedNodesByClassifier() const8570 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier()
8571 const {
8572 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
8573
8574 const nsTArray<nsWeakPtr> blockedNodes = mBlockedNodesByClassifier.Clone();
8575
8576 for (unsigned long i = 0; i < blockedNodes.Length(); i++) {
8577 nsWeakPtr weakNode = blockedNodes[i];
8578 nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode);
8579 // Consider only nodes to which we have managed to get strong references.
8580 // Coping with nullptrs since it's expected for nodes to disappear when
8581 // nobody else is referring to them.
8582 if (node) {
8583 list->AppendElement(node);
8584 }
8585 }
8586
8587 return list.forget();
8588 }
8589
GetSelectedStyleSheetSet(nsAString & aSheetSet)8590 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) {
8591 aSheetSet.Truncate();
8592
8593 // Look through our sheets, find the selected set title
8594 size_t count = SheetCount();
8595 nsAutoString title;
8596 for (size_t index = 0; index < count; index++) {
8597 StyleSheet* sheet = SheetAt(index);
8598 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8599
8600 if (sheet->Disabled()) {
8601 // Disabled sheets don't affect the currently selected set
8602 continue;
8603 }
8604
8605 sheet->GetTitle(title);
8606
8607 if (aSheetSet.IsEmpty()) {
8608 aSheetSet = title;
8609 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) {
8610 // Sheets from multiple sets enabled; return null string, per spec.
8611 SetDOMStringToNull(aSheetSet);
8612 return;
8613 }
8614 }
8615 }
8616
SetSelectedStyleSheetSet(const nsAString & aSheetSet)8617 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) {
8618 if (DOMStringIsNull(aSheetSet)) {
8619 return;
8620 }
8621
8622 // Must update mLastStyleSheetSet before doing anything else with stylesheets
8623 // or CSSLoaders.
8624 mLastStyleSheetSet = aSheetSet;
8625 EnableStyleSheetsForSetInternal(aSheetSet, true);
8626 }
8627
SetPreferredStyleSheetSet(const nsAString & aSheetSet)8628 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) {
8629 mPreferredStyleSheetSet = aSheetSet;
8630 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per
8631 // spec.
8632 if (DOMStringIsNull(mLastStyleSheetSet)) {
8633 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet,
8634 // per spec. The idea here is that we're changing our preferred set and
8635 // that shouldn't change the value of lastStyleSheetSet. Also, we're
8636 // using the Internal version so we can update the CSSLoader and not have
8637 // to worry about null strings.
8638 EnableStyleSheetsForSetInternal(aSheetSet, true);
8639 }
8640 }
8641
StyleSheetSets()8642 DOMStringList* Document::StyleSheetSets() {
8643 if (!mStyleSheetSetList) {
8644 mStyleSheetSetList = new DOMStyleSheetSetList(this);
8645 }
8646 return mStyleSheetSetList;
8647 }
8648
EnableStyleSheetsForSet(const nsAString & aSheetSet)8649 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) {
8650 // Per spec, passing in null is a no-op.
8651 if (!DOMStringIsNull(aSheetSet)) {
8652 // Note: must make sure to not change the CSSLoader's preferred sheet --
8653 // that value should be equal to either our lastStyleSheetSet (if that's
8654 // non-null) or to our preferredStyleSheetSet. And this method doesn't
8655 // change either of those.
8656 EnableStyleSheetsForSetInternal(aSheetSet, false);
8657 }
8658 }
8659
EnableStyleSheetsForSetInternal(const nsAString & aSheetSet,bool aUpdateCSSLoader)8660 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
8661 bool aUpdateCSSLoader) {
8662 size_t count = SheetCount();
8663 nsAutoString title;
8664 for (size_t index = 0; index < count; index++) {
8665 StyleSheet* sheet = SheetAt(index);
8666 NS_ASSERTION(sheet, "Null sheet in sheet list!");
8667
8668 sheet->GetTitle(title);
8669 if (!title.IsEmpty()) {
8670 sheet->SetEnabled(title.Equals(aSheetSet));
8671 }
8672 }
8673 if (aUpdateCSSLoader) {
8674 CSSLoader()->DocumentStyleSheetSetChanged();
8675 }
8676 if (mStyleSet->StyleSheetsHaveChanged()) {
8677 ApplicableStylesChanged();
8678 }
8679 }
8680
GetCharacterSet(nsAString & aCharacterSet) const8681 void Document::GetCharacterSet(nsAString& aCharacterSet) const {
8682 nsAutoCString charset;
8683 GetDocumentCharacterSet()->Name(charset);
8684 CopyASCIItoUTF16(charset, aCharacterSet);
8685 }
8686
ImportNode(nsINode & aNode,bool aDeep,ErrorResult & rv) const8687 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep,
8688 ErrorResult& rv) const {
8689 nsINode* imported = &aNode;
8690
8691 switch (imported->NodeType()) {
8692 case DOCUMENT_NODE: {
8693 break;
8694 }
8695 case DOCUMENT_FRAGMENT_NODE:
8696 case ATTRIBUTE_NODE:
8697 case ELEMENT_NODE:
8698 case PROCESSING_INSTRUCTION_NODE:
8699 case TEXT_NODE:
8700 case CDATA_SECTION_NODE:
8701 case COMMENT_NODE:
8702 case DOCUMENT_TYPE_NODE: {
8703 return imported->Clone(aDeep, mNodeInfoManager, rv);
8704 }
8705 default: {
8706 NS_WARNING("Don't know how to clone this nodetype for importNode.");
8707 }
8708 }
8709
8710 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
8711 return nullptr;
8712 }
8713
CreateRange(ErrorResult & rv)8714 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) {
8715 return nsRange::Create(this, 0, this, 0, rv);
8716 }
8717
CreateNodeIterator(nsINode & aRoot,uint32_t aWhatToShow,NodeFilter * aFilter,ErrorResult & rv) const8718 already_AddRefed<NodeIterator> Document::CreateNodeIterator(
8719 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter,
8720 ErrorResult& rv) const {
8721 RefPtr<NodeIterator> iterator =
8722 new NodeIterator(&aRoot, aWhatToShow, aFilter);
8723 return iterator.forget();
8724 }
8725
CreateTreeWalker(nsINode & aRoot,uint32_t aWhatToShow,NodeFilter * aFilter,ErrorResult & rv) const8726 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot,
8727 uint32_t aWhatToShow,
8728 NodeFilter* aFilter,
8729 ErrorResult& rv) const {
8730 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter);
8731 return walker.forget();
8732 }
8733
GetLocation() const8734 already_AddRefed<Location> Document::GetLocation() const {
8735 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
8736
8737 if (!w) {
8738 return nullptr;
8739 }
8740
8741 return do_AddRef(w->Location());
8742 }
8743
GetDomainURI()8744 already_AddRefed<nsIURI> Document::GetDomainURI() {
8745 nsIPrincipal* principal = NodePrincipal();
8746
8747 nsCOMPtr<nsIURI> uri;
8748 principal->GetDomain(getter_AddRefs(uri));
8749 if (uri) {
8750 return uri.forget();
8751 }
8752 auto* basePrin = BasePrincipal::Cast(principal);
8753 basePrin->GetURI(getter_AddRefs(uri));
8754 return uri.forget();
8755 }
8756
GetDomain(nsAString & aDomain)8757 void Document::GetDomain(nsAString& aDomain) {
8758 nsCOMPtr<nsIURI> uri = GetDomainURI();
8759
8760 if (!uri) {
8761 aDomain.Truncate();
8762 return;
8763 }
8764
8765 nsAutoCString hostName;
8766 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
8767 if (NS_SUCCEEDED(rv)) {
8768 CopyUTF8toUTF16(hostName, aDomain);
8769 } else {
8770 // If we can't get the host from the URI (e.g. about:, javascript:,
8771 // etc), just return an empty string.
8772 aDomain.Truncate();
8773 }
8774 }
8775
SetDomain(const nsAString & aDomain,ErrorResult & rv)8776 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) {
8777 if (!GetBrowsingContext()) {
8778 // If our browsing context is null; disallow setting domain
8779 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8780 return;
8781 }
8782
8783 if (mSandboxFlags & SANDBOXED_DOMAIN) {
8784 // We're sandboxed; disallow setting domain
8785 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8786 return;
8787 }
8788
8789 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) {
8790 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8791 return;
8792 }
8793
8794 if (aDomain.IsEmpty()) {
8795 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8796 return;
8797 }
8798
8799 nsCOMPtr<nsIURI> uri = GetDomainURI();
8800 if (!uri) {
8801 rv.Throw(NS_ERROR_FAILURE);
8802 return;
8803 }
8804
8805 // Check new domain - must be a superdomain of the current host
8806 // For example, a page from foo.bar.com may set domain to bar.com,
8807 // but not to ar.com, baz.com, or fi.foo.bar.com.
8808
8809 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
8810 if (!newURI) {
8811 // Error: illegal domain
8812 rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
8813 return;
8814 }
8815
8816 if (CrossOriginIsolated()) {
8817 WarnOnceAbout(Document::eDocumentSetDomainNotAllowed);
8818 return;
8819 }
8820
8821 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI));
8822 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI));
8823 WindowGlobalChild* wgc = GetWindowGlobalChild();
8824 if (wgc) {
8825 wgc->SendSetDocumentDomain(newURI);
8826 }
8827 }
8828
CreateInheritingURIForHost(const nsACString & aHostString)8829 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost(
8830 const nsACString& aHostString) {
8831 if (aHostString.IsEmpty()) {
8832 return nullptr;
8833 }
8834
8835 // Create new URI
8836 nsCOMPtr<nsIURI> uri = GetDomainURI();
8837 if (!uri) {
8838 return nullptr;
8839 }
8840
8841 nsresult rv;
8842 rv = NS_MutateURI(uri)
8843 .SetUserPass(""_ns)
8844 .SetPort(-1) // we want to reset the port number if needed.
8845 .SetHostPort(aHostString)
8846 .Finalize(uri);
8847 if (NS_FAILED(rv)) {
8848 return nullptr;
8849 }
8850
8851 return uri.forget();
8852 }
8853
RegistrableDomainSuffixOfInternal(const nsAString & aNewDomain,nsIURI * aOrigHost)8854 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal(
8855 const nsAString& aNewDomain, nsIURI* aOrigHost) {
8856 if (NS_WARN_IF(!aOrigHost)) {
8857 return nullptr;
8858 }
8859
8860 nsCOMPtr<nsIURI> newURI =
8861 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
8862 if (!newURI) {
8863 // Error: failed to parse input domain
8864 return nullptr;
8865 }
8866
8867 if (!IsValidDomain(aOrigHost, newURI)) {
8868 // Error: illegal domain
8869 return nullptr;
8870 }
8871
8872 nsAutoCString domain;
8873 if (NS_FAILED(newURI->GetAsciiHost(domain))) {
8874 return nullptr;
8875 }
8876
8877 return CreateInheritingURIForHost(domain);
8878 }
8879
8880 /* static */
IsValidDomain(nsIURI * aOrigHost,nsIURI * aNewURI)8881 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) {
8882 // Check new domain - must be a superdomain of the current host
8883 // For example, a page from foo.bar.com may set domain to bar.com,
8884 // but not to ar.com, baz.com, or fi.foo.bar.com.
8885 nsAutoCString current;
8886 nsAutoCString domain;
8887 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
8888 current.Truncate();
8889 }
8890 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) {
8891 domain.Truncate();
8892 }
8893
8894 bool ok = current.Equals(domain);
8895 if (current.Length() > domain.Length() && StringEndsWith(current, domain) &&
8896 current.CharAt(current.Length() - domain.Length() - 1) == '.') {
8897 // We're golden if the new domain is the current page's base domain or a
8898 // subdomain of it.
8899 nsCOMPtr<nsIEffectiveTLDService> tldService =
8900 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
8901 if (!tldService) {
8902 return false;
8903 }
8904
8905 nsAutoCString currentBaseDomain;
8906 ok = NS_SUCCEEDED(
8907 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
8908 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
8909 (domain.Length() >= currentBaseDomain.Length()),
8910 "uh-oh! slight optimization wasn't valid somehow!");
8911 ok = ok && domain.Length() >= currentBaseDomain.Length();
8912 }
8913
8914 return ok;
8915 }
8916
GetHtmlElement() const8917 Element* Document::GetHtmlElement() const {
8918 Element* rootElement = GetRootElement();
8919 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
8920 return rootElement;
8921 return nullptr;
8922 }
8923
GetHtmlChildElement(nsAtom * aTag)8924 Element* Document::GetHtmlChildElement(nsAtom* aTag) {
8925 Element* html = GetHtmlElement();
8926 if (!html) return nullptr;
8927
8928 // Look for the element with aTag inside html. This needs to run
8929 // forwards to find the first such element.
8930 for (nsIContent* child = html->GetFirstChild(); child;
8931 child = child->GetNextSibling()) {
8932 if (child->IsHTMLElement(aTag)) return child->AsElement();
8933 }
8934 return nullptr;
8935 }
8936
GetBody()8937 nsGenericHTMLElement* Document::GetBody() {
8938 Element* html = GetHtmlElement();
8939 if (!html) {
8940 return nullptr;
8941 }
8942
8943 for (nsIContent* child = html->GetFirstChild(); child;
8944 child = child->GetNextSibling()) {
8945 if (child->IsHTMLElement(nsGkAtoms::body) ||
8946 child->IsHTMLElement(nsGkAtoms::frameset)) {
8947 return static_cast<nsGenericHTMLElement*>(child);
8948 }
8949 }
8950
8951 return nullptr;
8952 }
8953
SetBody(nsGenericHTMLElement * newBody,ErrorResult & rv)8954 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) {
8955 nsCOMPtr<Element> root = GetRootElement();
8956
8957 // The body element must be either a body tag or a frameset tag. And we must
8958 // have a root element to be able to add kids to it.
8959 if (!newBody ||
8960 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) {
8961 rv.ThrowHierarchyRequestError(
8962 "The new body must be either a body tag or frameset tag.");
8963 return;
8964 }
8965
8966 if (!root) {
8967 rv.ThrowHierarchyRequestError("No root element.");
8968 return;
8969 }
8970
8971 // Use DOM methods so that we pass through the appropriate security checks.
8972 nsCOMPtr<Element> currentBody = GetBody();
8973 if (currentBody) {
8974 root->ReplaceChild(*newBody, *currentBody, rv);
8975 } else {
8976 root->AppendChild(*newBody, rv);
8977 }
8978 }
8979
GetHead()8980 HTMLSharedElement* Document::GetHead() {
8981 return static_cast<HTMLSharedElement*>(GetHeadElement());
8982 }
8983
GetTitleElement()8984 Element* Document::GetTitleElement() {
8985 // mMayHaveTitleElement will have been set to true if any HTML or SVG
8986 // <title> element has been bound to this document. So if it's false,
8987 // we know there is nothing to do here. This avoids us having to search
8988 // the whole DOM if someone calls document.title on a large document
8989 // without a title.
8990 if (!mMayHaveTitleElement) return nullptr;
8991
8992 Element* root = GetRootElement();
8993 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
8994 // In SVG, the document's title must be a child
8995 for (nsIContent* child = root->GetFirstChild(); child;
8996 child = child->GetNextSibling()) {
8997 if (child->IsSVGElement(nsGkAtoms::title)) {
8998 return child->AsElement();
8999 }
9000 }
9001 return nullptr;
9002 }
9003
9004 // We check the HTML namespace even for non-HTML documents, except SVG. This
9005 // matches the spec and the behavior of all tested browsers.
9006 // We avoid creating a live nsContentList since we don't need to watch for DOM
9007 // tree mutations.
9008 RefPtr<nsContentList> list = new nsContentList(
9009 this, kNameSpaceID_XHTML, nsGkAtoms::title, nsGkAtoms::title,
9010 /* aDeep = */ true,
9011 /* aLiveList = */ false);
9012
9013 nsIContent* first = list->Item(0, false);
9014
9015 return first ? first->AsElement() : nullptr;
9016 }
9017
GetTitle(nsAString & aTitle)9018 void Document::GetTitle(nsAString& aTitle) {
9019 aTitle.Truncate();
9020
9021 Element* rootElement = GetRootElement();
9022 if (!rootElement) {
9023 return;
9024 }
9025
9026 nsAutoString tmp;
9027
9028 if (rootElement->IsXULElement()) {
9029 rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp);
9030 } else {
9031 Element* title = GetTitleElement();
9032 if (!title) {
9033 return;
9034 }
9035 nsContentUtils::GetNodeTextContent(title, false, tmp);
9036 }
9037
9038 tmp.CompressWhitespace();
9039 aTitle = tmp;
9040 }
9041
SetTitle(const nsAString & aTitle,ErrorResult & aRv)9042 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) {
9043 Element* rootElement = GetRootElement();
9044 if (!rootElement) {
9045 return;
9046 }
9047
9048 if (rootElement->IsXULElement()) {
9049 aRv =
9050 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true);
9051 return;
9052 }
9053
9054 Maybe<mozAutoDocUpdate> updateBatch;
9055 nsCOMPtr<Element> title = GetTitleElement();
9056 if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
9057 if (!title) {
9058 // Batch updates so that mutation events don't change "the title
9059 // element" under us
9060 updateBatch.emplace(this, true);
9061 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(
9062 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE);
9063 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
9064 NOT_FROM_PARSER);
9065 if (!title) {
9066 return;
9067 }
9068 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true,
9069 IgnoreErrors());
9070 }
9071 } else if (rootElement->IsHTMLElement()) {
9072 if (!title) {
9073 // Batch updates so that mutation events don't change "the title
9074 // element" under us
9075 updateBatch.emplace(this, true);
9076 Element* head = GetHeadElement();
9077 if (!head) {
9078 return;
9079 }
9080
9081 RefPtr<mozilla::dom::NodeInfo> titleInfo;
9082 titleInfo = mNodeInfoManager->GetNodeInfo(
9083 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE);
9084 title = NS_NewHTMLTitleElement(titleInfo.forget());
9085 if (!title) {
9086 return;
9087 }
9088
9089 head->AppendChildTo(title, true, IgnoreErrors());
9090 }
9091 } else {
9092 return;
9093 }
9094
9095 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false);
9096 }
9097
NotifyPossibleTitleChange(bool aBoundTitleElement)9098 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) {
9099 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement,
9100 "Setting a title while unlinking or destroying the element?");
9101 if (mInUnlinkOrDeletion) {
9102 return;
9103 }
9104
9105 if (aBoundTitleElement) {
9106 mMayHaveTitleElement = true;
9107 }
9108 if (mPendingTitleChangeEvent.IsPending()) return;
9109
9110 MOZ_RELEASE_ASSERT(NS_IsMainThread());
9111 RefPtr<nsRunnableMethod<Document, void, false>> event =
9112 NewNonOwningRunnableMethod("Document::DoNotifyPossibleTitleChange", this,
9113 &Document::DoNotifyPossibleTitleChange);
9114 nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(event));
9115 if (NS_SUCCEEDED(rv)) {
9116 mPendingTitleChangeEvent = std::move(event);
9117 }
9118 }
9119
DoNotifyPossibleTitleChange()9120 void Document::DoNotifyPossibleTitleChange() {
9121 if (!mPendingTitleChangeEvent.IsPending()) {
9122 return;
9123 }
9124 // Make sure the pending runnable method is cleared.
9125 mPendingTitleChangeEvent.Revoke();
9126 mHaveFiredTitleChange = true;
9127
9128 nsAutoString title;
9129 GetTitle(title);
9130
9131 RefPtr<PresShell> presShell = GetPresShell();
9132 if (presShell) {
9133 nsCOMPtr<nsISupports> container =
9134 presShell->GetPresContext()->GetContainerWeak();
9135 if (container) {
9136 nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container);
9137 if (docShellWin) {
9138 docShellWin->SetTitle(title);
9139 }
9140 }
9141 }
9142
9143 if (nsPIDOMWindowInner* inner = GetInnerWindow()) {
9144 if (WindowGlobalChild* child = inner->GetWindowGlobalChild()) {
9145 child->SendUpdateDocumentTitle(title);
9146 }
9147 }
9148
9149 // Fire a DOM event for the title change.
9150 nsContentUtils::DispatchChromeEvent(this, ToSupports(this),
9151 u"DOMTitleChanged"_ns, CanBubble::eYes,
9152 Cancelable::eYes);
9153
9154 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
9155 if (obs) {
9156 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr);
9157 }
9158 }
9159
MatchMedia(const nsACString & aMediaQueryList,CallerType aCallerType)9160 already_AddRefed<MediaQueryList> Document::MatchMedia(
9161 const nsACString& aMediaQueryList, CallerType aCallerType) {
9162 RefPtr<MediaQueryList> result =
9163 new MediaQueryList(this, aMediaQueryList, aCallerType);
9164
9165 mDOMMediaQueryLists.insertBack(result);
9166
9167 return result.forget();
9168 }
9169
SetMayStartLayout(bool aMayStartLayout)9170 void Document::SetMayStartLayout(bool aMayStartLayout) {
9171 mMayStartLayout = aMayStartLayout;
9172 if (MayStartLayout()) {
9173 // Before starting layout, check whether we're a toplevel chrome
9174 // window. If we are, setup some state so that we don't have to restyle
9175 // the whole tree after StartLayout.
9176 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) {
9177 // We're the chrome document!
9178 win->BeforeStartLayout();
9179 }
9180 ReadyState state = GetReadyStateEnum();
9181 if (state >= READYSTATE_INTERACTIVE) {
9182 // DOMContentLoaded has fired already.
9183 MaybeResolveReadyForIdle();
9184 }
9185 }
9186
9187 MaybeEditingStateChanged();
9188 }
9189
InitializeFrameLoader(nsFrameLoader * aLoader)9190 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) {
9191 mInitializableFrameLoaders.RemoveElement(aLoader);
9192 // Don't even try to initialize.
9193 if (mInDestructor) {
9194 NS_WARNING(
9195 "Trying to initialize a frame loader while"
9196 "document is being deleted");
9197 return NS_ERROR_FAILURE;
9198 }
9199
9200 mInitializableFrameLoaders.AppendElement(aLoader);
9201 if (!mFrameLoaderRunner) {
9202 mFrameLoaderRunner =
9203 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9204 &Document::MaybeInitializeFinalizeFrameLoaders);
9205 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9206 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9207 }
9208 return NS_OK;
9209 }
9210
FinalizeFrameLoader(nsFrameLoader * aLoader,nsIRunnable * aFinalizer)9211 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader,
9212 nsIRunnable* aFinalizer) {
9213 mInitializableFrameLoaders.RemoveElement(aLoader);
9214 if (mInDestructor) {
9215 return NS_ERROR_FAILURE;
9216 }
9217
9218 LogRunnable::LogDispatch(aFinalizer);
9219 mFrameLoaderFinalizers.AppendElement(aFinalizer);
9220 if (!mFrameLoaderRunner) {
9221 mFrameLoaderRunner =
9222 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this,
9223 &Document::MaybeInitializeFinalizeFrameLoaders);
9224 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
9225 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9226 }
9227 return NS_OK;
9228 }
9229
MaybeInitializeFinalizeFrameLoaders()9230 void Document::MaybeInitializeFinalizeFrameLoaders() {
9231 if (mDelayFrameLoaderInitialization) {
9232 // This method will be recalled when !mDelayFrameLoaderInitialization.
9233 mFrameLoaderRunner = nullptr;
9234 return;
9235 }
9236
9237 // We're not in an update, but it is not safe to run scripts, so
9238 // postpone frameloader initialization and finalization.
9239 if (!nsContentUtils::IsSafeToRunScript()) {
9240 if (!mInDestructor && !mFrameLoaderRunner &&
9241 (mInitializableFrameLoaders.Length() ||
9242 mFrameLoaderFinalizers.Length())) {
9243 mFrameLoaderRunner = NewRunnableMethod(
9244 "Document::MaybeInitializeFinalizeFrameLoaders", this,
9245 &Document::MaybeInitializeFinalizeFrameLoaders);
9246 nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
9247 }
9248 return;
9249 }
9250 mFrameLoaderRunner = nullptr;
9251
9252 // Don't use a temporary array for mInitializableFrameLoaders, because
9253 // loading a frame may cause some other frameloader to be removed from the
9254 // array. But be careful to keep the loader alive when starting the load!
9255 while (mInitializableFrameLoaders.Length()) {
9256 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
9257 mInitializableFrameLoaders.RemoveElementAt(0);
9258 NS_ASSERTION(loader, "null frameloader in the array?");
9259 loader->ReallyStartLoading();
9260 }
9261
9262 uint32_t length = mFrameLoaderFinalizers.Length();
9263 if (length > 0) {
9264 nsTArray<nsCOMPtr<nsIRunnable>> finalizers =
9265 std::move(mFrameLoaderFinalizers);
9266 for (uint32_t i = 0; i < length; ++i) {
9267 LogRunnable::Run run(finalizers[i]);
9268 finalizers[i]->Run();
9269 }
9270 }
9271 }
9272
TryCancelFrameLoaderInitialization(nsIDocShell * aShell)9273 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) {
9274 uint32_t length = mInitializableFrameLoaders.Length();
9275 for (uint32_t i = 0; i < length; ++i) {
9276 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
9277 mInitializableFrameLoaders.RemoveElementAt(i);
9278 return;
9279 }
9280 }
9281 }
9282
SetPrototypeDocument(nsXULPrototypeDocument * aPrototype)9283 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) {
9284 mPrototypeDocument = aPrototype;
9285 mSynchronousDOMContentLoaded = true;
9286 }
9287
PermDelegateHandler()9288 nsIPermissionDelegateHandler* Document::PermDelegateHandler() {
9289 return GetPermissionDelegateHandler();
9290 }
9291
RequestExternalResource(nsIURI * aURI,nsIReferrerInfo * aReferrerInfo,nsINode * aRequestingNode,ExternalResourceLoad ** aPendingLoad)9292 Document* Document::RequestExternalResource(
9293 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode,
9294 ExternalResourceLoad** aPendingLoad) {
9295 MOZ_ASSERT(aURI, "Must have a URI");
9296 MOZ_ASSERT(aRequestingNode, "Must have a node");
9297 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo");
9298 if (mDisplayDocument) {
9299 return mDisplayDocument->RequestExternalResource(
9300 aURI, aReferrerInfo, aRequestingNode, aPendingLoad);
9301 }
9302
9303 return mExternalResourceMap.RequestResource(
9304 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad);
9305 }
9306
EnumerateExternalResources(SubDocEnumFunc aCallback)9307 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) {
9308 mExternalResourceMap.EnumerateResources(aCallback);
9309 }
9310
GetAnimationController()9311 SMILAnimationController* Document::GetAnimationController() {
9312 // We create the animation controller lazily because most documents won't want
9313 // one and only SVG documents and the like will call this
9314 if (mAnimationController) return mAnimationController;
9315 // Refuse to create an Animation Controller for data documents.
9316 if (mLoadedAsData) return nullptr;
9317
9318 mAnimationController = new SMILAnimationController(this);
9319
9320 // If there's a presContext then check the animation mode and pause if
9321 // necessary.
9322 nsPresContext* context = GetPresContext();
9323 if (mAnimationController && context &&
9324 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
9325 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF);
9326 }
9327
9328 // If we're hidden (or being hidden), notify the newly-created animation
9329 // controller. (Skip this check for SVG-as-an-image documents, though,
9330 // because they don't get OnPageShow / OnPageHide calls).
9331 if (!mIsShowing && !mIsBeingUsedAsImage) {
9332 mAnimationController->OnPageHide();
9333 }
9334
9335 return mAnimationController;
9336 }
9337
GetOrCreatePendingAnimationTracker()9338 PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
9339 if (!mPendingAnimationTracker) {
9340 mPendingAnimationTracker = new PendingAnimationTracker(this);
9341 }
9342
9343 return mPendingAnimationTracker;
9344 }
9345
9346 /**
9347 * Retrieve the "direction" property of the document.
9348 *
9349 * @lina 01/09/2001
9350 */
GetDir(nsAString & aDirection) const9351 void Document::GetDir(nsAString& aDirection) const {
9352 aDirection.Truncate();
9353 Element* rootElement = GetHtmlElement();
9354 if (rootElement) {
9355 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection);
9356 }
9357 }
9358
9359 /**
9360 * Set the "direction" property of the document.
9361 *
9362 * @lina 01/09/2001
9363 */
SetDir(const nsAString & aDirection)9364 void Document::SetDir(const nsAString& aDirection) {
9365 Element* rootElement = GetHtmlElement();
9366 if (rootElement) {
9367 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true);
9368 }
9369 }
9370
Images()9371 nsIHTMLCollection* Document::Images() {
9372 if (!mImages) {
9373 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img,
9374 nsGkAtoms::img);
9375 }
9376 return mImages;
9377 }
9378
Embeds()9379 nsIHTMLCollection* Document::Embeds() {
9380 if (!mEmbeds) {
9381 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed,
9382 nsGkAtoms::embed);
9383 }
9384 return mEmbeds;
9385 }
9386
MatchLinks(Element * aElement,int32_t aNamespaceID,nsAtom * aAtom,void * aData)9387 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9388 void* aData) {
9389 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
9390 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
9391 }
9392
Links()9393 nsIHTMLCollection* Document::Links() {
9394 if (!mLinks) {
9395 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
9396 }
9397 return mLinks;
9398 }
9399
Forms()9400 nsIHTMLCollection* Document::Forms() {
9401 if (!mForms) {
9402 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls.
9403 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form,
9404 nsGkAtoms::form);
9405 }
9406
9407 return mForms;
9408 }
9409
Scripts()9410 nsIHTMLCollection* Document::Scripts() {
9411 if (!mScripts) {
9412 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script,
9413 nsGkAtoms::script);
9414 }
9415 return mScripts;
9416 }
9417
Applets()9418 nsIHTMLCollection* Document::Applets() {
9419 if (!mApplets) {
9420 mApplets = new nsEmptyContentList(this);
9421 }
9422 return mApplets;
9423 }
9424
MatchAnchors(Element * aElement,int32_t aNamespaceID,nsAtom * aAtom,void * aData)9425 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom,
9426 void* aData) {
9427 return aElement->IsHTMLElement(nsGkAtoms::a) &&
9428 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
9429 }
9430
Anchors()9431 nsIHTMLCollection* Document::Anchors() {
9432 if (!mAnchors) {
9433 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
9434 }
9435 return mAnchors;
9436 }
9437
Open(const nsAString & aURL,const nsAString & aName,const nsAString & aFeatures,ErrorResult & rv)9438 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open(
9439 const nsAString& aURL, const nsAString& aName, const nsAString& aFeatures,
9440 ErrorResult& rv) {
9441 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9442 "XOW should have caught this!");
9443
9444 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
9445 if (!window) {
9446 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
9447 return nullptr;
9448 }
9449 nsCOMPtr<nsPIDOMWindowOuter> outer =
9450 nsPIDOMWindowOuter::GetFromCurrentInner(window);
9451 if (!outer) {
9452 rv.Throw(NS_ERROR_NOT_INITIALIZED);
9453 return nullptr;
9454 }
9455 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer);
9456 RefPtr<BrowsingContext> newBC;
9457 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC));
9458 if (!newBC) {
9459 return nullptr;
9460 }
9461 return WindowProxyHolder(std::move(newBC));
9462 }
9463
Open(const Optional<nsAString> &,const Optional<nsAString> &,ErrorResult & aError)9464 Document* Document::Open(const Optional<nsAString>& /* unused */,
9465 const Optional<nsAString>& /* unused */,
9466 ErrorResult& aError) {
9467 // Implements
9468 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
9469
9470 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this),
9471 "XOW should have caught this!");
9472
9473 // Step 1 -- throw if we're an XML document.
9474 if (!IsHTMLDocument() || mDisableDocWrite) {
9475 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9476 return nullptr;
9477 }
9478
9479 // Step 2 -- throw if dynamic markup insertion should throw.
9480 if (ShouldThrowOnDynamicMarkupInsertion()) {
9481 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9482 return nullptr;
9483 }
9484
9485 // Step 3 -- get the entry document, so we can use it for security checks.
9486 nsCOMPtr<Document> callerDoc = GetEntryDocument();
9487 if (!callerDoc) {
9488 // If we're called from C++ or in some other way without an originating
9489 // document we can't do a document.open w/o changing the principal of the
9490 // document to something like about:blank (as that's the only sane thing to
9491 // do when we don't know the origin of this call), and since we can't
9492 // change the principals of a document for security reasons we'll have to
9493 // refuse to go ahead with this call.
9494
9495 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9496 return nullptr;
9497 }
9498
9499 // Step 4 -- make sure we're same-origin (not just same origin-domain) with
9500 // the entry document.
9501 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
9502 aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
9503 return nullptr;
9504 }
9505
9506 // Step 5 -- if we have an active parser with a nonzero script nesting level,
9507 // just no-op.
9508 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) {
9509 return this;
9510 }
9511
9512 // Step 6 -- check for open() during unload. Per spec, this is just a check
9513 // of the ignore-opens-during-unload counter, but our unload event code
9514 // doesn't affect that counter yet (unlike pagehide and beforeunload, which
9515 // do), so we check for unload directly.
9516 if (ShouldIgnoreOpens()) {
9517 return this;
9518 }
9519
9520 RefPtr<nsDocShell> shell(mDocumentContainer);
9521 if (shell) {
9522 bool inUnload;
9523 shell->GetIsInUnload(&inUnload);
9524 if (inUnload) {
9525 return this;
9526 }
9527 }
9528
9529 // document.open() inherits the CSP from the opening document.
9530 // Please create an actual copy of the CSP (do not share the same
9531 // reference) otherwise appending a new policy within the opened
9532 // document will be incorrectly propagated to the opening doc.
9533 nsCOMPtr<nsIContentSecurityPolicy> csp = callerDoc->GetCsp();
9534 if (csp) {
9535 RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
9536 cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
9537 mCSP = cspToInherit;
9538 }
9539
9540 // At this point we know this is a valid-enough document.open() call
9541 // and not a no-op. Increment our use counter.
9542 SetUseCounter(eUseCounter_custom_DocumentOpen);
9543
9544 // Step 7 -- stop existing navigation of our browsing context (and all other
9545 // loads it's doing) if we're the active document of our browsing context.
9546 // Note that we do not want to stop anything if there is no existing
9547 // navigation.
9548 if (shell && IsCurrentActiveDocument() &&
9549 shell->GetIsAttemptingToNavigate()) {
9550 shell->Stop(nsIWebNavigation::STOP_NETWORK);
9551
9552 // The Stop call may have cancelled the onload blocker request or
9553 // prevented it from getting added, so we need to make sure it gets added
9554 // to the document again otherwise the document could have a non-zero
9555 // onload block count without the onload blocker request being in the
9556 // loadgroup.
9557 EnsureOnloadBlocker();
9558 }
9559
9560 // Step 8 -- clear event listeners out of our DOM tree
9561 for (nsINode* node : ShadowIncludingTreeIterator(*this)) {
9562 if (EventListenerManager* elm = node->GetExistingListenerManager()) {
9563 elm->RemoveAllListeners();
9564 }
9565 }
9566
9567 // Step 9 -- clear event listeners from our window, if we have one.
9568 //
9569 // Note that we explicitly want the inner window, and only if we're its
9570 // document. We want to do this (per spec) even when we're not the "active
9571 // document", so we can't go through GetWindow(), because it might forward to
9572 // the wrong inner.
9573 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
9574 if (win->GetExtantDoc() == this) {
9575 if (EventListenerManager* elm =
9576 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) {
9577 elm->RemoveAllListeners();
9578 }
9579 }
9580 }
9581
9582 // If we have a parser that has a zero script nesting level, we need to
9583 // properly terminate it. We do that after we've removed all the event
9584 // listeners (so termination won't trigger event listeners if it does
9585 // something to the DOM), but before we remove all elements from the document
9586 // (so if termination does modify the DOM in some way we will just blow it
9587 // away immediately. See the similar code in WriteCommon that handles the
9588 // !IsInsertionPointDefined() case and should stay in sync with this code.
9589 if (mParser) {
9590 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(),
9591 "Why didn't we take the early return?");
9592 // Make sure we don't re-enter.
9593 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9594 mParser->Terminate();
9595 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9596 }
9597
9598 // Step 10 -- remove all our DOM kids without firing any mutation events.
9599 {
9600 // We want to ignore any recursive calls to Open() that happen while
9601 // disconnecting the node tree. The spec doesn't say to do this, but the
9602 // spec also doesn't envision unload events on subframes firing while we do
9603 // this, while all browsers fire them in practice. See
9604 // <https://github.com/whatwg/html/issues/4611>.
9605 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9606 DisconnectNodeTree();
9607 }
9608
9609 // Step 11 -- if we're the current document in our docshell, do the
9610 // equivalent of pushState() with the new URL we should have.
9611 if (shell && IsCurrentActiveDocument()) {
9612 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
9613 if (callerDoc != this) {
9614 nsCOMPtr<nsIURI> noFragmentURI;
9615 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI));
9616 if (NS_WARN_IF(NS_FAILED(rv))) {
9617 aError.Throw(rv);
9618 return nullptr;
9619 }
9620 newURI = std::move(noFragmentURI);
9621 }
9622
9623 // UpdateURLAndHistory might do various member-setting, so make sure we're
9624 // holding strong refs to all the refcounted args on the stack. We can
9625 // assume that our caller is holding on to "this" already.
9626 nsCOMPtr<nsIURI> currentURI = GetDocumentURI();
9627 bool equalURIs;
9628 nsresult rv = currentURI->Equals(newURI, &equalURIs);
9629 if (NS_WARN_IF(NS_FAILED(rv))) {
9630 aError.Throw(rv);
9631 return nullptr;
9632 }
9633 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
9634 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, u""_ns,
9635 /* aReplace = */ true, currentURI,
9636 equalURIs);
9637 if (NS_WARN_IF(NS_FAILED(rv))) {
9638 aError.Throw(rv);
9639 return nullptr;
9640 }
9641
9642 // And use the security info of the caller document as well, since
9643 // it's the thing providing our data.
9644 mSecurityInfo = callerDoc->GetSecurityInfo();
9645
9646 // This is not mentioned in the spec, but I think that's a spec bug. See
9647 // <https://github.com/whatwg/html/issues/4299>. In any case, since our
9648 // URL may be changing away from about:blank here, we really want to unset
9649 // this flag no matter what, since only about:blank can be an initial
9650 // document.
9651 SetIsInitialDocument(false);
9652
9653 // And let our docloader know that it will need to track our load event.
9654 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
9655 }
9656
9657 // Per spec nothing happens with our URI in other cases, though note
9658 // <https://github.com/whatwg/html/issues/4286>.
9659
9660 // Note that we don't need to do anything here with base URIs per spec.
9661 // That said, this might be assuming that we implement
9662 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url
9663 // correctly, which we don't right now for the about:blank case.
9664
9665 // Step 12, but note <https://github.com/whatwg/html/issues/4292>.
9666 mSkipLoadEventAfterClose = mLoadEventFiring;
9667
9668 // Preliminary to steps 13-16. Set our ready state to uninitialized before
9669 // we do anything else, so we can then proceed to later ready state levels.
9670 SetReadyStateInternal(READYSTATE_UNINITIALIZED,
9671 /* updateTimingInformation = */ false);
9672 // Reset a flag that affects readyState behavior.
9673 mSetCompleteAfterDOMContentLoaded = false;
9674
9675 // Step 13 -- set our compat mode to standards.
9676 SetCompatibilityMode(eCompatibility_FullStandards);
9677
9678 // Step 14 -- create a new parser associated with document. This also does
9679 // step 16 implicitly.
9680 mParserAborted = false;
9681 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser();
9682 mParser = parser;
9683 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr);
9684 nsresult rv = parser->StartExecutor();
9685 if (NS_WARN_IF(NS_FAILED(rv))) {
9686 aError.Throw(rv);
9687 return nullptr;
9688 }
9689
9690 // Clear out our form control state, because the state of controls
9691 // in the pre-open() document should not affect the state of
9692 // controls that are now going to be written.
9693 mLayoutHistoryState = nullptr;
9694
9695 if (shell) {
9696 // Prepare the docshell and the document viewer for the impending
9697 // out-of-band document.write()
9698 shell->PrepareForNewContentModel();
9699
9700 nsCOMPtr<nsIContentViewer> cv;
9701 shell->GetContentViewer(getter_AddRefs(cv));
9702 if (cv) {
9703 cv->LoadStart(this);
9704 }
9705 }
9706
9707 // Step 15.
9708 SetReadyStateInternal(Document::READYSTATE_LOADING,
9709 /* updateTimingInformation = */ false);
9710
9711 // Step 16 happened with step 14 above.
9712
9713 // Step 17.
9714 return this;
9715 }
9716
Close(ErrorResult & rv)9717 void Document::Close(ErrorResult& rv) {
9718 if (!IsHTMLDocument()) {
9719 // No calling document.close() on XHTML!
9720
9721 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9722 return;
9723 }
9724
9725 if (ShouldThrowOnDynamicMarkupInsertion()) {
9726 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9727 return;
9728 }
9729
9730 if (!mParser || !mParser->IsScriptCreated()) {
9731 return;
9732 }
9733
9734 ++mWriteLevel;
9735 rv = (static_cast<nsHtml5Parser*>(mParser.get()))
9736 ->Parse(u""_ns, nullptr, true);
9737 --mWriteLevel;
9738 }
9739
WriteCommon(const Sequence<nsString> & aText,bool aNewlineTerminate,mozilla::ErrorResult & rv)9740 void Document::WriteCommon(const Sequence<nsString>& aText,
9741 bool aNewlineTerminate, mozilla::ErrorResult& rv) {
9742 // Fast path the common case
9743 if (aText.Length() == 1) {
9744 WriteCommon(aText[0], aNewlineTerminate, rv);
9745 } else {
9746 // XXXbz it would be nice if we could pass all the strings to the parser
9747 // without having to do all this copying and then ask it to start
9748 // parsing....
9749 nsString text;
9750 for (size_t i = 0; i < aText.Length(); ++i) {
9751 text.Append(aText[i]);
9752 }
9753 WriteCommon(text, aNewlineTerminate, rv);
9754 }
9755 }
9756
WriteCommon(const nsAString & aText,bool aNewlineTerminate,ErrorResult & aRv)9757 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate,
9758 ErrorResult& aRv) {
9759 #ifdef DEBUG
9760 {
9761 // Assert that we do not use or accidentally introduce doc.write()
9762 // in system privileged context or in any of our about: pages.
9763 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
9764 bool isAboutOrPrivContext = principal->IsSystemPrincipal();
9765 if (!isAboutOrPrivContext) {
9766 if (principal->SchemeIs("about")) {
9767 // about:blank inherits the security contetext and this assertion
9768 // is only meant for actual about: pages.
9769 nsAutoCString host;
9770 principal->GetHost(host);
9771 isAboutOrPrivContext = !host.EqualsLiteral("blank");
9772 }
9773 }
9774 // Some automated tests use an empty string to kick off some parsing
9775 // mechansims, but they do not do any harm since they use an empty string.
9776 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(),
9777 "do not use doc.write in privileged context!");
9778 }
9779 #endif
9780
9781 mTooDeepWriteRecursion =
9782 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
9783 if (NS_WARN_IF(mTooDeepWriteRecursion)) {
9784 aRv.Throw(NS_ERROR_UNEXPECTED);
9785 return;
9786 }
9787
9788 if (!IsHTMLDocument() || mDisableDocWrite) {
9789 // No calling document.write*() on XHTML!
9790
9791 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9792 return;
9793 }
9794
9795 if (ShouldThrowOnDynamicMarkupInsertion()) {
9796 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
9797 return;
9798 }
9799
9800 if (mParserAborted) {
9801 // Hixie says aborting the parser doesn't undefine the insertion point.
9802 // However, since we null out mParser in that case, we track the
9803 // theoretically defined insertion point using mParserAborted.
9804 return;
9805 }
9806
9807 // Implement Step 4.1 of:
9808 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
9809 if (ShouldIgnoreOpens()) {
9810 return;
9811 }
9812
9813 void* key = GenerateParserKey();
9814 if (mParser && !mParser->IsInsertionPointDefined()) {
9815 if (mIgnoreDestructiveWritesCounter) {
9816 // Instead of implying a call to document.open(), ignore the call.
9817 nsContentUtils::ReportToConsole(
9818 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9819 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9820 return;
9821 }
9822 // The spec doesn't tell us to ignore opens from here, but we need to
9823 // ensure opens are ignored here. See similar code in Open() that handles
9824 // the case of an existing parser which is not currently running script and
9825 // should stay in sync with this code.
9826 IgnoreOpensDuringUnload ignoreOpenGuard(this);
9827 mParser->Terminate();
9828 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out");
9829 }
9830
9831 if (!mParser) {
9832 if (mIgnoreDestructiveWritesCounter) {
9833 // Instead of implying a call to document.open(), ignore the call.
9834 nsContentUtils::ReportToConsole(
9835 nsIScriptError::warningFlag, "DOM Events"_ns, this,
9836 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored");
9837 return;
9838 }
9839
9840 Open({}, {}, aRv);
9841
9842 // If Open() fails, or if it didn't create a parser (as it won't
9843 // if the user chose to not discard the current document through
9844 // onbeforeunload), don't write anything.
9845 if (aRv.Failed() || !mParser) {
9846 return;
9847 }
9848 }
9849
9850 static constexpr auto new_line = u"\n"_ns;
9851
9852 ++mWriteLevel;
9853
9854 // This could be done with less code, but for performance reasons it
9855 // makes sense to have the code for two separate Parse() calls here
9856 // since the concatenation of strings costs more than we like. And
9857 // why pay that price when we don't need to?
9858 if (aNewlineTerminate) {
9859 aRv = (static_cast<nsHtml5Parser*>(mParser.get()))
9860 ->Parse(aText + new_line, key, false);
9861 } else {
9862 aRv =
9863 (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(aText, key, false);
9864 }
9865
9866 --mWriteLevel;
9867
9868 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
9869 }
9870
Write(const Sequence<nsString> & aText,ErrorResult & rv)9871 void Document::Write(const Sequence<nsString>& aText, ErrorResult& rv) {
9872 WriteCommon(aText, false, rv);
9873 }
9874
Writeln(const Sequence<nsString> & aText,ErrorResult & rv)9875 void Document::Writeln(const Sequence<nsString>& aText, ErrorResult& rv) {
9876 WriteCommon(aText, true, rv);
9877 }
9878
GenerateParserKey(void)9879 void* Document::GenerateParserKey(void) {
9880 if (!mScriptLoader) {
9881 // If we don't have a script loader, then the parser probably isn't parsing
9882 // anything anyway, so just return null.
9883 return nullptr;
9884 }
9885
9886 // The script loader provides us with the currently executing script element,
9887 // which is guaranteed to be unique per script.
9888 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
9889 if (script && mParser && mParser->IsScriptCreated()) {
9890 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
9891 if (creatorParser != mParser) {
9892 // Make scripts that aren't inserted by the active parser of this document
9893 // participate in the context of the script that document.open()ed
9894 // this document.
9895 return nullptr;
9896 }
9897 }
9898 return script;
9899 }
9900
9901 /* static */
MatchNameAttribute(Element * aElement,int32_t aNamespaceID,nsAtom * aAtom,void * aData)9902 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID,
9903 nsAtom* aAtom, void* aData) {
9904 MOZ_ASSERT(aElement, "Must have element to work with!");
9905
9906 if (!aElement->HasName()) {
9907 return false;
9908 }
9909
9910 nsString* elementName = static_cast<nsString*>(aData);
9911 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
9912 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName,
9913 eCaseMatters);
9914 }
9915
9916 /* static */
UseExistingNameString(nsINode * aRootNode,const nsString * aName)9917 void* Document::UseExistingNameString(nsINode* aRootNode,
9918 const nsString* aName) {
9919 return const_cast<nsString*>(aName);
9920 }
9921
GetDocumentURI(nsString & aDocumentURI) const9922 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const {
9923 if (mDocumentURI) {
9924 nsAutoCString uri;
9925 nsresult rv = mDocumentURI->GetSpec(uri);
9926 NS_ENSURE_SUCCESS(rv, rv);
9927
9928 CopyUTF8toUTF16(uri, aDocumentURI);
9929 } else {
9930 aDocumentURI.Truncate();
9931 }
9932
9933 return NS_OK;
9934 }
9935
9936 // Alias of above
GetURL(nsString & aURL) const9937 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); }
9938
GetDocumentURIFromJS(nsString & aDocumentURI,CallerType aCallerType,ErrorResult & aRv) const9939 void Document::GetDocumentURIFromJS(nsString& aDocumentURI,
9940 CallerType aCallerType,
9941 ErrorResult& aRv) const {
9942 if (!mChromeXHRDocURI || aCallerType != CallerType::System) {
9943 aRv = GetDocumentURI(aDocumentURI);
9944 return;
9945 }
9946
9947 nsAutoCString uri;
9948 nsresult res = mChromeXHRDocURI->GetSpec(uri);
9949 if (NS_FAILED(res)) {
9950 aRv.Throw(res);
9951 return;
9952 }
9953 CopyUTF8toUTF16(uri, aDocumentURI);
9954 }
9955
GetDocumentURIObject() const9956 nsIURI* Document::GetDocumentURIObject() const {
9957 if (!mChromeXHRDocURI) {
9958 return GetDocumentURI();
9959 }
9960
9961 return mChromeXHRDocURI;
9962 }
9963
GetCompatMode(nsString & aCompatMode) const9964 void Document::GetCompatMode(nsString& aCompatMode) const {
9965 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks ||
9966 mCompatMode == eCompatibility_AlmostStandards ||
9967 mCompatMode == eCompatibility_FullStandards,
9968 "mCompatMode is neither quirks nor strict for this document");
9969
9970 if (mCompatMode == eCompatibility_NavQuirks) {
9971 aCompatMode.AssignLiteral("BackCompat");
9972 } else {
9973 aCompatMode.AssignLiteral("CSS1Compat");
9974 }
9975 }
9976
9977 } // namespace dom
9978 } // namespace mozilla
9979
BlastSubtreeToPieces(nsINode * aNode)9980 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) {
9981 if (Element* element = Element::FromNode(aNode)) {
9982 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) {
9983 while (true) {
9984 RefPtr<Attr> attr;
9985 {
9986 // Use an iterator to get an arbitrary attribute from the
9987 // cache. The iterator must be destroyed before any other
9988 // operations on mAttributeCache, to avoid hash table
9989 // assertions.
9990 auto iter = map->mAttributeCache.ConstIter();
9991 if (iter.Done()) {
9992 break;
9993 }
9994 attr = iter.UserData();
9995 }
9996
9997 BlastSubtreeToPieces(attr);
9998
9999 mozilla::DebugOnly<nsresult> rv =
10000 element->UnsetAttr(attr->NodeInfo()->NamespaceID(),
10001 attr->NodeInfo()->NameAtom(), false);
10002
10003 // XXX Should we abort here?
10004 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
10005 }
10006 }
10007
10008 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) {
10009 BlastSubtreeToPieces(shadow);
10010 element->UnattachShadow();
10011 }
10012 }
10013
10014 while (aNode->HasChildren()) {
10015 nsIContent* node = aNode->GetFirstChild();
10016 BlastSubtreeToPieces(node);
10017 aNode->RemoveChildNode(node, false);
10018 }
10019 }
10020
10021 namespace mozilla::dom {
10022
AdoptNode(nsINode & aAdoptedNode,ErrorResult & rv)10023 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) {
10024 OwningNonNull<nsINode> adoptedNode = aAdoptedNode;
10025
10026 // Scope firing mutation events so that we don't carry any state that
10027 // might be stale
10028 {
10029 if (nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode()) {
10030 nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
10031 }
10032 }
10033
10034 nsAutoScriptBlocker scriptBlocker;
10035
10036 switch (adoptedNode->NodeType()) {
10037 case ATTRIBUTE_NODE: {
10038 // Remove from ownerElement.
10039 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode);
10040
10041 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement(rv);
10042 if (rv.Failed()) {
10043 return nullptr;
10044 }
10045
10046 if (ownerElement) {
10047 OwningNonNull<Attr> newAttr =
10048 ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
10049 if (rv.Failed()) {
10050 return nullptr;
10051 }
10052 }
10053
10054 break;
10055 }
10056 case DOCUMENT_FRAGMENT_NODE: {
10057 if (adoptedNode->IsShadowRoot()) {
10058 rv.ThrowHierarchyRequestError("The adopted node is a shadow root.");
10059 return nullptr;
10060 }
10061 [[fallthrough]];
10062 }
10063 case ELEMENT_NODE:
10064 case PROCESSING_INSTRUCTION_NODE:
10065 case TEXT_NODE:
10066 case CDATA_SECTION_NODE:
10067 case COMMENT_NODE:
10068 case DOCUMENT_TYPE_NODE: {
10069 // Don't allow adopting a node's anonymous subtree out from under it.
10070 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) {
10071 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10072 return nullptr;
10073 }
10074
10075 // We don't want to adopt an element into its own contentDocument or into
10076 // a descendant contentDocument, so we check if the frameElement of this
10077 // document or any of its parents is the adopted node or one of its
10078 // descendants.
10079 RefPtr<BrowsingContext> bc = GetBrowsingContext();
10080 while (bc) {
10081 nsCOMPtr<nsINode> node = bc->GetEmbedderElement();
10082 if (node && node->IsInclusiveDescendantOf(adoptedNode)) {
10083 rv.ThrowHierarchyRequestError(
10084 "Trying to adopt a node into its own contentDocument or a "
10085 "descendant contentDocument.");
10086 return nullptr;
10087 }
10088
10089 if (XRE_IsParentProcess()) {
10090 bc = bc->Canonical()->GetParentCrossChromeBoundary();
10091 } else {
10092 bc = bc->GetParent();
10093 }
10094 }
10095
10096 // Remove from parent.
10097 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
10098 if (parent) {
10099 parent->RemoveChildNode(adoptedNode->AsContent(), true);
10100 } else {
10101 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
10102 }
10103
10104 break;
10105 }
10106 case DOCUMENT_NODE: {
10107 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10108 return nullptr;
10109 }
10110 default: {
10111 NS_WARNING("Don't know how to adopt this nodetype for adoptNode.");
10112
10113 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10114 return nullptr;
10115 }
10116 }
10117
10118 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc();
10119 bool sameDocument = oldDocument == this;
10120
10121 AutoJSContext cx;
10122 JS::Rooted<JSObject*> newScope(cx, nullptr);
10123 if (!sameDocument) {
10124 newScope = GetWrapper();
10125 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) {
10126 // Make sure cx is in a semi-sane compartment before we call WrapNative.
10127 // It's kind of irrelevant, given that we're passing aAllowWrapping =
10128 // false, and documents should always insist on being wrapped in an
10129 // canonical scope. But we try to pass something sane anyway.
10130 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject();
10131 JSAutoRealm ar(cx, globalObject);
10132 JS::Rooted<JS::Value> v(cx);
10133 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v,
10134 /* aAllowWrapping = */ false);
10135 if (rv.Failed()) return nullptr;
10136 newScope = &v.toObject();
10137 }
10138 }
10139
10140 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv);
10141 if (rv.Failed()) {
10142 // Disconnect all nodes from their parents, since some have the old document
10143 // as their ownerDocument and some have this as their ownerDocument.
10144 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
10145 return nullptr;
10146 }
10147
10148 MOZ_ASSERT(adoptedNode->OwnerDoc() == this,
10149 "Should still be in the document we just got adopted into");
10150
10151 return adoptedNode;
10152 }
10153
UseWidthDeviceWidthFallbackViewport() const10154 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; }
10155
ParseScaleString(const nsString & aScaleString)10156 static Maybe<LayoutDeviceToScreenScale> ParseScaleString(
10157 const nsString& aScaleString) {
10158 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale
10159 if (aScaleString.EqualsLiteral("device-width") ||
10160 aScaleString.EqualsLiteral("device-height")) {
10161 return Some(LayoutDeviceToScreenScale(10.0f));
10162 } else if (aScaleString.EqualsLiteral("yes")) {
10163 return Some(LayoutDeviceToScreenScale(1.0f));
10164 } else if (aScaleString.EqualsLiteral("no")) {
10165 return Some(LayoutDeviceToScreenScale(kViewportMinScale));
10166 } else if (aScaleString.IsEmpty()) {
10167 return Nothing();
10168 }
10169
10170 nsresult scaleErrorCode;
10171 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode);
10172 if (NS_FAILED(scaleErrorCode)) {
10173 return Some(LayoutDeviceToScreenScale(kViewportMinScale));
10174 }
10175
10176 if (scale < 0) {
10177 return Nothing();
10178 }
10179 return Some(clamped(LayoutDeviceToScreenScale(scale), kViewportMinScale,
10180 kViewportMaxScale));
10181 }
10182
ParseScalesInViewportMetaData(const ViewportMetaData & aViewportMetaData)10183 void Document::ParseScalesInViewportMetaData(
10184 const ViewportMetaData& aViewportMetaData) {
10185 Maybe<LayoutDeviceToScreenScale> scale;
10186
10187 scale = ParseScaleString(aViewportMetaData.mInitialScale);
10188 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f));
10189 mValidScaleFloat = scale.isSome();
10190
10191 scale = ParseScaleString(aViewportMetaData.mMaximumScale);
10192 // Chrome uses '5' for the fallback value of maximum-scale, we might
10193 // consider matching it in future.
10194 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0
10195 mScaleMaxFloat = scale.valueOr(kViewportMaxScale);
10196 mValidMaxScale = scale.isSome();
10197
10198 scale = ParseScaleString(aViewportMetaData.mMinimumScale);
10199 mScaleMinFloat = scale.valueOr(kViewportMinScale);
10200 mValidMinScale = scale.isSome();
10201
10202 // Resolve min-zoom and max-zoom values.
10203 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom
10204 if (mValidMaxScale && mValidMinScale) {
10205 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat);
10206 }
10207 }
10208
ParseWidthAndHeightInMetaViewport(const nsAString & aWidthString,const nsAString & aHeightString,bool aHasValidScale)10209 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString,
10210 const nsAString& aHeightString,
10211 bool aHasValidScale) {
10212 // The width and height properties
10213 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties
10214 //
10215 // The width and height viewport <META> properties are translated into width
10216 // and height descriptors, setting the min-width/min-height value to
10217 // extend-to-zoom and the max-width/max-height value to the length from the
10218 // viewport <META> property as follows:
10219 //
10220 // 1. Non-negative number values are translated to pixel lengths, clamped to
10221 // the range: [1px, 10000px]
10222 // 2. Negative number values are dropped
10223 // 3. device-width and device-height translate to 100vw and 100vh respectively
10224 // 4. Other keywords and unknown values are also dropped
10225 mMinWidth = nsViewportInfo::Auto;
10226 mMaxWidth = nsViewportInfo::Auto;
10227 if (!aWidthString.IsEmpty()) {
10228 mMinWidth = nsViewportInfo::ExtendToZoom;
10229 if (aWidthString.EqualsLiteral("device-width")) {
10230 mMaxWidth = nsViewportInfo::DeviceSize;
10231 } else {
10232 nsresult widthErrorCode;
10233 mMaxWidth = aWidthString.ToInteger(&widthErrorCode);
10234 if (NS_FAILED(widthErrorCode)) {
10235 mMaxWidth = nsViewportInfo::Auto;
10236 } else if (mMaxWidth >= 0.0f) {
10237 mMaxWidth = clamped(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f));
10238 } else {
10239 mMaxWidth = nsViewportInfo::Auto;
10240 }
10241 }
10242 } else if (aHasValidScale) {
10243 if (aHeightString.IsEmpty()) {
10244 mMinWidth = nsViewportInfo::ExtendToZoom;
10245 mMaxWidth = nsViewportInfo::ExtendToZoom;
10246 }
10247 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) {
10248 mMinWidth = nsViewportInfo::ExtendToZoom;
10249 mMaxWidth = nsViewportInfo::DeviceSize;
10250 }
10251
10252 mMinHeight = nsViewportInfo::Auto;
10253 mMaxHeight = nsViewportInfo::Auto;
10254 if (!aHeightString.IsEmpty()) {
10255 mMinHeight = nsViewportInfo::ExtendToZoom;
10256 if (aHeightString.EqualsLiteral("device-height")) {
10257 mMaxHeight = nsViewportInfo::DeviceSize;
10258 } else {
10259 nsresult heightErrorCode;
10260 mMaxHeight = aHeightString.ToInteger(&heightErrorCode);
10261 if (NS_FAILED(heightErrorCode)) {
10262 mMaxHeight = nsViewportInfo::Auto;
10263 } else if (mMaxHeight >= 0.0f) {
10264 mMaxHeight = clamped(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f));
10265 } else {
10266 mMaxHeight = nsViewportInfo::Auto;
10267 }
10268 }
10269 }
10270 }
10271
GetViewportInfo(const ScreenIntSize & aDisplaySize)10272 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) {
10273 MOZ_ASSERT(mPresShell);
10274
10275 // Compute the CSS-to-LayoutDevice pixel scale as the product of the
10276 // widget scale and the full zoom.
10277 nsPresContext* context = mPresShell->GetPresContext();
10278 // When querying the full zoom, get it from the device context rather than
10279 // directly from the pres context, because the device context's value can
10280 // include an adjustment necessay to keep the number of app units per device
10281 // pixel an integer, and we want the adjusted value.
10282 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0;
10283 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom;
10284 CSSToLayoutDeviceScale layoutDeviceScale =
10285 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1);
10286
10287 CSSToScreenScale defaultScale =
10288 layoutDeviceScale * LayoutDeviceToScreenScale(1.0);
10289
10290 // Special behaviour for desktop mode, provided we are not on an about: page,
10291 // or fullscreen.
10292 const bool fullscreen = Fullscreen();
10293 nsPIDOMWindowOuter* win = GetWindow();
10294 if (win && win->IsDesktopModeViewport() && !IsAboutPage() && !fullscreen) {
10295 CSSCoord viewportWidth =
10296 StaticPrefs::browser_viewport_desktopWidth() / fullZoom;
10297 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth);
10298 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width;
10299 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio);
10300 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit);
10301 return nsViewportInfo(fakeDesktopSize, scaleToFit,
10302 nsViewportInfo::ZoomFlag::AllowZoom,
10303 nsViewportInfo::ZoomBehaviour::Mobile);
10304 }
10305
10306 // We ignore viewport meta tage etc when in fullscreen, see bug 1696717.
10307 if (fullscreen || !nsLayoutUtils::ShouldHandleMetaViewport(this)) {
10308 return nsViewportInfo(aDisplaySize, defaultScale,
10309 nsLayoutUtils::AllowZoomingForDocument(this)
10310 ? nsViewportInfo::ZoomFlag::AllowZoom
10311 : nsViewportInfo::ZoomFlag::DisallowZoom,
10312 StaticPrefs::apz_allow_zooming_out()
10313 ? nsViewportInfo::ZoomBehaviour::Mobile
10314 : nsViewportInfo::ZoomBehaviour::Desktop);
10315 }
10316
10317 // In cases where the width of the CSS viewport is less than or equal to the
10318 // width of the display (i.e. width <= device-width) then we disable
10319 // double-tap-to-zoom behaviour. See bug 941995 for details.
10320
10321 switch (mViewportType) {
10322 case DisplayWidthHeight:
10323 return nsViewportInfo(aDisplaySize, defaultScale,
10324 nsViewportInfo::ZoomFlag::AllowZoom,
10325 nsViewportInfo::ZoomBehaviour::Mobile);
10326 case Unknown: {
10327 // We might early exit if the viewport is empty. Even if we don't,
10328 // at the end of this case we'll note that it was empty. Later, when
10329 // we're using the cached values, this will trigger alternate code paths.
10330 if (!mLastModifiedViewportMetaData) {
10331 // If the docType specifies that we are on a site optimized for mobile,
10332 // then we want to return specially crafted defaults for the viewport
10333 // info.
10334 if (RefPtr<DocumentType> docType = GetDoctype()) {
10335 nsAutoString docId;
10336 docType->GetPublicId(docId);
10337 if ((docId.Find("WAP") != -1) || (docId.Find("Mobile") != -1) ||
10338 (docId.Find("WML") != -1)) {
10339 // We're making an assumption that the docType can't change here
10340 mViewportType = DisplayWidthHeight;
10341 return nsViewportInfo(aDisplaySize, defaultScale,
10342 nsViewportInfo::ZoomFlag::AllowZoom,
10343 nsViewportInfo::ZoomBehaviour::Mobile);
10344 }
10345 }
10346
10347 nsAutoString handheldFriendly;
10348 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
10349 if (handheldFriendly.EqualsLiteral("true")) {
10350 mViewportType = DisplayWidthHeight;
10351 return nsViewportInfo(aDisplaySize, defaultScale,
10352 nsViewportInfo::ZoomFlag::AllowZoom,
10353 nsViewportInfo::ZoomBehaviour::Mobile);
10354 }
10355 }
10356
10357 ViewportMetaData metaData = GetViewportMetaData();
10358
10359 // Parse initial-scale, minimum-scale and maximum-scale.
10360 ParseScalesInViewportMetaData(metaData);
10361
10362 // Parse width and height properties
10363 // This function sets m{Min,Max}{Width,Height}.
10364 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight,
10365 mValidScaleFloat);
10366
10367 mAllowZoom = true;
10368 if ((metaData.mUserScalable.EqualsLiteral("0")) ||
10369 (metaData.mUserScalable.EqualsLiteral("no")) ||
10370 (metaData.mUserScalable.EqualsLiteral("false"))) {
10371 mAllowZoom = false;
10372 }
10373
10374 // Resolve viewport-fit value.
10375 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
10376 mViewportFit = ViewportFitType::Auto;
10377 if (!metaData.mViewportFit.IsEmpty()) {
10378 if (metaData.mViewportFit.EqualsLiteral("contain")) {
10379 mViewportFit = ViewportFitType::Contain;
10380 } else if (metaData.mViewportFit.EqualsLiteral("cover")) {
10381 mViewportFit = ViewportFitType::Cover;
10382 }
10383 }
10384
10385 mWidthStrEmpty = metaData.mWidth.IsEmpty();
10386
10387 mViewportType = Specified;
10388 [[fallthrough]];
10389 }
10390 case Specified:
10391 default:
10392 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
10393 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
10394 bool effectiveValidMaxScale = mValidMaxScale;
10395
10396 nsViewportInfo::ZoomFlag effectiveZoomFlag =
10397 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom
10398 : nsViewportInfo::ZoomFlag::DisallowZoom;
10399 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) {
10400 // If the pref to force user-scalable is enabled, we ignore the values
10401 // from the meta-viewport tag for these properties and just assume they
10402 // allow the page to be scalable. Note in particular that this code is
10403 // in the "Specified" branch of the enclosing switch statement, so that
10404 // calls to GetViewportInfo always use the latest value of the
10405 // browser_ui_zoom_force_user_scalable pref. Other codepaths that
10406 // return nsViewportInfo instances are all consistent with
10407 // browser_ui_zoom_force_user_scalable() already.
10408 effectiveMinScale = kViewportMinScale;
10409 effectiveMaxScale = kViewportMaxScale;
10410 effectiveValidMaxScale = true;
10411 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom;
10412 }
10413
10414 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat).
10415 auto ComputeExtendZoom = [&]() -> float {
10416 if (mValidScaleFloat && effectiveValidMaxScale) {
10417 return std::min(mScaleFloat.scale, effectiveMaxScale.scale);
10418 }
10419 if (mValidScaleFloat) {
10420 return mScaleFloat.scale;
10421 }
10422 if (effectiveValidMaxScale) {
10423 return effectiveMaxScale.scale;
10424 }
10425 return nsViewportInfo::Auto;
10426 };
10427
10428 // Resolving 'extend-to-zoom'
10429 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom
10430 float extendZoom = ComputeExtendZoom();
10431
10432 CSSCoord minWidth = mMinWidth;
10433 CSSCoord maxWidth = mMaxWidth;
10434 CSSCoord minHeight = mMinHeight;
10435 CSSCoord maxHeight = mMaxHeight;
10436
10437 // aDisplaySize is in screen pixels; convert them to CSS pixels for the
10438 // viewport size. We need to use this scaled size for any clamping of
10439 // width or height.
10440 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale;
10441
10442 // Our min and max width and height values are mostly as specified by
10443 // the viewport declaration, but we make an exception for max width.
10444 // Max width, if auto, and if there's no initial-scale, will be set
10445 // to a default size. This is to support legacy site design with no
10446 // viewport declaration, and to do that using the same scheme as
10447 // Chrome does, in order to maintain web compatibility. Since the
10448 // default size has a complicated calculation, we fixup the maxWidth
10449 // value after setting it, above.
10450 if (maxWidth == nsViewportInfo::Auto && !mValidScaleFloat) {
10451 BrowsingContext* bc = GetBrowsingContext();
10452 if (bc && bc->TouchEventsOverride() == TouchEventsOverride::Enabled &&
10453 bc->InRDMPane()) {
10454 // If RDM and touch simulation are active, then use the simulated
10455 // screen width to accomodate for cases where the screen width is
10456 // larger than the desktop viewport default.
10457 maxWidth = nsViewportInfo::Max(
10458 displaySize.width, StaticPrefs::browser_viewport_desktopWidth());
10459 } else {
10460 maxWidth = StaticPrefs::browser_viewport_desktopWidth();
10461 }
10462 // Divide by fullZoom to stretch CSS pixel size of viewport in order
10463 // to keep device pixel size unchanged after full zoom applied.
10464 // See bug 974242.
10465 maxWidth /= fullZoom;
10466
10467 // We set minWidth to ExtendToZoom, which will cause our later width
10468 // calculation to expand to maxWidth, if scale restrictions allow it.
10469 minWidth = nsViewportInfo::ExtendToZoom;
10470 }
10471
10472 // Resolve device-width and device-height first.
10473 if (maxWidth == nsViewportInfo::DeviceSize) {
10474 maxWidth = displaySize.width;
10475 }
10476 if (maxHeight == nsViewportInfo::DeviceSize) {
10477 maxHeight = displaySize.height;
10478 }
10479 if (extendZoom == nsViewportInfo::Auto) {
10480 if (maxWidth == nsViewportInfo::ExtendToZoom) {
10481 maxWidth = nsViewportInfo::Auto;
10482 }
10483 if (maxHeight == nsViewportInfo::ExtendToZoom) {
10484 maxHeight = nsViewportInfo::Auto;
10485 }
10486 if (minWidth == nsViewportInfo::ExtendToZoom) {
10487 minWidth = maxWidth;
10488 }
10489 if (minHeight == nsViewportInfo::ExtendToZoom) {
10490 minHeight = maxHeight;
10491 }
10492 } else {
10493 CSSSize extendSize = displaySize / extendZoom;
10494 if (maxWidth == nsViewportInfo::ExtendToZoom) {
10495 maxWidth = extendSize.width;
10496 }
10497 if (maxHeight == nsViewportInfo::ExtendToZoom) {
10498 maxHeight = extendSize.height;
10499 }
10500 if (minWidth == nsViewportInfo::ExtendToZoom) {
10501 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth);
10502 }
10503 if (minHeight == nsViewportInfo::ExtendToZoom) {
10504 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight);
10505 }
10506 }
10507
10508 // Resolve initial width and height from min/max descriptors
10509 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height
10510 CSSCoord width = nsViewportInfo::Auto;
10511 if (minWidth != nsViewportInfo::Auto ||
10512 maxWidth != nsViewportInfo::Auto) {
10513 width = nsViewportInfo::Max(
10514 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width));
10515 }
10516 CSSCoord height = nsViewportInfo::Auto;
10517 if (minHeight != nsViewportInfo::Auto ||
10518 maxHeight != nsViewportInfo::Auto) {
10519 height = nsViewportInfo::Max(
10520 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height));
10521 }
10522
10523 // Resolve width value
10524 // https://drafts.csswg.org/css-device-adapt/#resolve-width
10525 if (width == nsViewportInfo::Auto) {
10526 if (height == nsViewportInfo::Auto || aDisplaySize.height == 0) {
10527 width = displaySize.width;
10528 } else {
10529 width = height * aDisplaySize.width / aDisplaySize.height;
10530 }
10531 }
10532
10533 // Resolve height value
10534 // https://drafts.csswg.org/css-device-adapt/#resolve-height
10535 if (height == nsViewportInfo::Auto) {
10536 if (aDisplaySize.width == 0) {
10537 height = displaySize.height;
10538 } else {
10539 height = width * aDisplaySize.height / aDisplaySize.width;
10540 }
10541 }
10542 MOZ_ASSERT(width != nsViewportInfo::Auto &&
10543 height != nsViewportInfo::Auto);
10544
10545 CSSSize size(width, height);
10546
10547 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
10548 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
10549 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
10550
10551 nsViewportInfo::AutoSizeFlag sizeFlag =
10552 nsViewportInfo::AutoSizeFlag::FixedSize;
10553 if (mMaxWidth == nsViewportInfo::DeviceSize ||
10554 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::DeviceSize ||
10555 mScaleFloat.scale == 1.0f)) ||
10556 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::Auto &&
10557 mMaxHeight < 0)) {
10558 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize;
10559 }
10560
10561 // FIXME: Resolving width and height should be done above 'Resolve width
10562 // value' and 'Resolve height value'.
10563 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) {
10564 size = displaySize;
10565 }
10566
10567 // The purpose of clamping the viewport width to a minimum size is to
10568 // prevent page authors from setting it to a ridiculously small value.
10569 // If the page is actually being rendered in a very small area (as might
10570 // happen in e.g. Android 8's picture-in-picture mode), we don't want to
10571 // prevent the viewport from taking on that size.
10572 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize);
10573
10574 size.width = clamped(size.width, effectiveMinSize.width,
10575 float(kViewportMaxSize.width));
10576
10577 // Also recalculate the default zoom, if it wasn't specified in the
10578 // metadata, and the width is specified.
10579 if (!mValidScaleFloat && !mWidthStrEmpty) {
10580 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width);
10581 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale;
10582 }
10583
10584 size.height = clamped(size.height, effectiveMinSize.height,
10585 float(kViewportMaxSize.height));
10586
10587 // In cases of user-scalable=no, if we have a positive scale, clamp it to
10588 // min and max, and then use the clamped value for the scale, the min, and
10589 // the max. If we don't have a positive scale, assert that we are setting
10590 // the auto scale flag.
10591 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom &&
10592 scaleFloat > CSSToScreenScale(0.0f)) {
10593 scaleFloat = scaleMinFloat = scaleMaxFloat =
10594 clamped(scaleFloat, scaleMinFloat, scaleMaxFloat);
10595 }
10596 MOZ_ASSERT(
10597 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat,
10598 "If we don't have a positive scale, we should be using auto scale.");
10599
10600 // We need to perform a conversion, but only if the initial or maximum
10601 // scale were set explicitly by the user.
10602 if (mValidScaleFloat && scaleFloat >= scaleMinFloat &&
10603 scaleFloat <= scaleMaxFloat) {
10604 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat;
10605 size.width = std::max(size.width, displaySize.width);
10606 size.height = std::max(size.height, displaySize.height);
10607 } else if (effectiveValidMaxScale) {
10608 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat;
10609 size.width = std::max(size.width, displaySize.width);
10610 size.height = std::max(size.height, displaySize.height);
10611 }
10612
10613 return nsViewportInfo(
10614 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag,
10615 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale
10616 : nsViewportInfo::AutoScaleFlag::AutoScale,
10617 effectiveZoomFlag, mViewportFit);
10618 }
10619 }
10620
GetViewportMetaData() const10621 ViewportMetaData Document::GetViewportMetaData() const {
10622 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData
10623 : ViewportMetaData();
10624 }
10625
SetMetaViewportData(UniquePtr<ViewportMetaData> aData)10626 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
10627 mLastModifiedViewportMetaData = std::move(aData);
10628 // Trigger recomputation of the nsViewportInfo the next time it's queried.
10629 mViewportType = Unknown;
10630
10631 RefPtr<AsyncEventDispatcher> asyncDispatcher =
10632 new AsyncEventDispatcher(this, u"DOMMetaViewportFitChanged"_ns,
10633 CanBubble::eYes, ChromeOnlyDispatch::eYes);
10634 asyncDispatcher->RunDOMEventWhenSafe();
10635 }
10636
GetOrCreateListenerManager()10637 EventListenerManager* Document::GetOrCreateListenerManager() {
10638 if (!mListenerManager) {
10639 mListenerManager =
10640 new EventListenerManager(static_cast<EventTarget*>(this));
10641 SetFlags(NODE_HAS_LISTENERMANAGER);
10642 }
10643
10644 return mListenerManager;
10645 }
10646
GetExistingListenerManager() const10647 EventListenerManager* Document::GetExistingListenerManager() const {
10648 return mListenerManager;
10649 }
10650
GetEventTargetParent(EventChainPreVisitor & aVisitor)10651 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
10652 aVisitor.mCanHandle = true;
10653 // FIXME! This is a hack to make middle mouse paste working also in Editor.
10654 // Bug 329119
10655 aVisitor.mForceContentDispatch = true;
10656
10657 // Load events must not propagate to |window| object, see bug 335251.
10658 if (aVisitor.mEvent->mMessage != eLoad) {
10659 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
10660 aVisitor.SetParentTarget(
10661 window ? window->GetTargetForEventTargetChain() : nullptr, false);
10662 }
10663 }
10664
CreateEvent(const nsAString & aEventType,CallerType aCallerType,ErrorResult & rv) const10665 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType,
10666 CallerType aCallerType,
10667 ErrorResult& rv) const {
10668 nsPresContext* presContext = GetPresContext();
10669
10670 // Create event even without presContext.
10671 RefPtr<Event> ev =
10672 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext,
10673 nullptr, aEventType, aCallerType);
10674 if (!ev) {
10675 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
10676 return nullptr;
10677 }
10678 WidgetEvent* e = ev->WidgetEventPtr();
10679 e->mFlags.mBubbles = false;
10680 e->mFlags.mCancelable = false;
10681 return ev.forget();
10682 }
10683
FlushPendingNotifications(FlushType aType)10684 void Document::FlushPendingNotifications(FlushType aType) {
10685 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
10686 FlushPendingNotifications(flush);
10687 }
10688
10689 class nsDocumentOnStack {
10690 public:
nsDocumentOnStack(Document * aDoc)10691 explicit nsDocumentOnStack(Document* aDoc) : mDoc(aDoc) {
10692 mDoc->IncreaseStackRefCnt();
10693 }
~nsDocumentOnStack()10694 ~nsDocumentOnStack() { mDoc->DecreaseStackRefCnt(); }
10695
10696 private:
10697 Document* mDoc;
10698 };
10699
FlushPendingNotifications(mozilla::ChangesToFlush aFlush)10700 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
10701 FlushType flushType = aFlush.mFlushType;
10702
10703 nsDocumentOnStack dos(this);
10704
10705 // We need to flush the sink for non-HTML documents (because the XML
10706 // parser still does insertion with deferred notifications). We
10707 // also need to flush the sink if this is a layout-related flush, to
10708 // make sure that layout is started as needed. But we can skip that
10709 // part if we have no presshell or if it's already done an initial
10710 // reflow.
10711 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify &&
10712 mPresShell && !mPresShell->DidInitialize())) &&
10713 (mParser || mWeakSink)) {
10714 nsCOMPtr<nsIContentSink> sink;
10715 if (mParser) {
10716 sink = mParser->GetContentSink();
10717 } else {
10718 sink = do_QueryReferent(mWeakSink);
10719 if (!sink) {
10720 mWeakSink = nullptr;
10721 }
10722 }
10723 // Determine if it is safe to flush the sink notifications
10724 // by determining if it safe to flush all the presshells.
10725 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) {
10726 sink->FlushPendingNotifications(flushType);
10727 }
10728 }
10729
10730 // Should we be flushing pending binding constructors in here?
10731
10732 if (flushType <= FlushType::ContentAndNotify) {
10733 // Nothing to do here
10734 return;
10735 }
10736
10737 // If we have a parent we must flush the parent too to ensure that our
10738 // container is reflowed if its size was changed.
10739 //
10740 // We do it only if the subdocument and the parent can observe each other
10741 // synchronously (that is, if we're not cross-origin), to avoid work that is
10742 // not observable, and if the parent document has finished loading all its
10743 // render-blocking stylesheets and may start laying out the document, to avoid
10744 // unnecessary flashes of unstyled content on the parent document. Note that
10745 // this last bit means that size-dependent media queries in this document may
10746 // produce incorrect results temporarily.
10747 //
10748 // But if it's not safe to flush ourselves, then don't flush the parent, since
10749 // that can cause things like resizes of our frame's widget, which we can't
10750 // handle while flushing is unsafe.
10751 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
10752 mParentDocument->MayStartLayout() && IsSafeToFlush()) {
10753 ChangesToFlush parentFlush = aFlush;
10754 if (flushType >= FlushType::Style) {
10755 // Since media queries mean that a size change of our container can affect
10756 // style, we need to promote a style flush on ourself to a layout flush on
10757 // our parent, since we need our container to be the correct size to
10758 // determine the correct style.
10759 parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
10760 }
10761 mParentDocument->FlushPendingNotifications(parentFlush);
10762 }
10763
10764 if (RefPtr<PresShell> presShell = GetPresShell()) {
10765 presShell->FlushPendingNotifications(aFlush);
10766 }
10767 }
10768
FlushExternalResources(FlushType aType)10769 void Document::FlushExternalResources(FlushType aType) {
10770 NS_ASSERTION(
10771 aType >= FlushType::Style,
10772 "should only need to flush for style or higher in external resources");
10773 if (GetDisplayDocument()) {
10774 return;
10775 }
10776
10777 auto flush = [aType](Document& aDoc) {
10778 aDoc.FlushPendingNotifications(aType);
10779 return CallState::Continue;
10780 };
10781
10782 EnumerateExternalResources(flush);
10783 }
10784
SetXMLDeclaration(const char16_t * aVersion,const char16_t * aEncoding,const int32_t aStandalone)10785 void Document::SetXMLDeclaration(const char16_t* aVersion,
10786 const char16_t* aEncoding,
10787 const int32_t aStandalone) {
10788 if (!aVersion || *aVersion == '\0') {
10789 mXMLDeclarationBits = 0;
10790 return;
10791 }
10792
10793 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS;
10794
10795 if (aEncoding && *aEncoding != '\0') {
10796 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS;
10797 }
10798
10799 if (aStandalone == 1) {
10800 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS |
10801 XML_DECLARATION_BITS_STANDALONE_YES;
10802 } else if (aStandalone == 0) {
10803 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS;
10804 }
10805 }
10806
GetXMLDeclaration(nsAString & aVersion,nsAString & aEncoding,nsAString & aStandalone)10807 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
10808 nsAString& aStandalone) {
10809 aVersion.Truncate();
10810 aEncoding.Truncate();
10811 aStandalone.Truncate();
10812
10813 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) {
10814 return;
10815 }
10816
10817 // always until we start supporting 1.1 etc.
10818 aVersion.AssignLiteral("1.0");
10819
10820 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) {
10821 // This is what we have stored, not necessarily what was written
10822 // in the original
10823 GetCharacterSet(aEncoding);
10824 }
10825
10826 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) {
10827 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) {
10828 aStandalone.AssignLiteral("yes");
10829 } else {
10830 aStandalone.AssignLiteral("no");
10831 }
10832 }
10833 }
10834
AddColorSchemeMeta(HTMLMetaElement & aMeta)10835 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) {
10836 mColorSchemeMetaTags.Insert(aMeta);
10837 RecomputeColorScheme();
10838 }
10839
RemoveColorSchemeMeta(HTMLMetaElement & aMeta)10840 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) {
10841 mColorSchemeMetaTags.RemoveElement(aMeta);
10842 RecomputeColorScheme();
10843 }
10844
RecomputeColorScheme()10845 void Document::RecomputeColorScheme() {
10846 if (!StaticPrefs::layout_css_color_scheme_enabled()) {
10847 return;
10848 }
10849 auto oldColorScheme = mColorSchemeBits;
10850 mColorSchemeBits = 0;
10851 const nsTArray<HTMLMetaElement*>& elements = mColorSchemeMetaTags;
10852 for (const HTMLMetaElement* el : elements) {
10853 nsAutoString content;
10854 if (!el->GetAttr(nsGkAtoms::content, content)) {
10855 continue;
10856 }
10857
10858 NS_ConvertUTF16toUTF8 contentU8(content);
10859 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) {
10860 break;
10861 }
10862 }
10863
10864 if (mColorSchemeBits == oldColorScheme) {
10865 return;
10866 }
10867
10868 if (nsPresContext* pc = GetPresContext()) {
10869 // This affects system colors, which are inherited, so we need to recascade.
10870 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree());
10871 }
10872 }
10873
IsScriptEnabled()10874 bool Document::IsScriptEnabled() {
10875 // If this document is sandboxed without 'allow-scripts'
10876 // script is not enabled
10877 if (HasScriptsBlockedBySandbox()) {
10878 return false;
10879 }
10880
10881 nsCOMPtr<nsIScriptGlobalObject> globalObject =
10882 do_QueryInterface(GetInnerWindow());
10883 if (!globalObject || !globalObject->HasJSGlobal()) {
10884 return false;
10885 }
10886
10887 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor())
10888 .Allowed();
10889 }
10890
RetrieveRelevantHeaders(nsIChannel * aChannel)10891 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
10892 PRTime modDate = 0;
10893 nsresult rv;
10894
10895 nsCOMPtr<nsIHttpChannel> httpChannel;
10896 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
10897 if (NS_WARN_IF(NS_FAILED(rv))) {
10898 return;
10899 }
10900
10901 if (httpChannel) {
10902 nsAutoCString tmp;
10903 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp);
10904
10905 if (NS_SUCCEEDED(rv)) {
10906 PRTime time;
10907 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
10908 if (st == PR_SUCCESS) {
10909 modDate = time;
10910 }
10911 }
10912
10913 static const char* const headers[] = {
10914 "default-style", "content-style-type", "content-language",
10915 "content-disposition", "refresh", "x-dns-prefetch-control",
10916 "x-frame-options", "origin-trial",
10917 // add more http headers if you need
10918 // XXXbz don't add content-location support without reading bug
10919 // 238654 and its dependencies/dups first.
10920 0};
10921
10922 nsAutoCString headerVal;
10923 const char* const* name = headers;
10924 while (*name) {
10925 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal);
10926 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) {
10927 RefPtr<nsAtom> key = NS_Atomize(*name);
10928 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal));
10929 }
10930 ++name;
10931 }
10932 } else {
10933 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel);
10934 if (fileChannel) {
10935 nsCOMPtr<nsIFile> file;
10936 fileChannel->GetFile(getter_AddRefs(file));
10937 if (file) {
10938 PRTime msecs;
10939 rv = file->GetLastModifiedTime(&msecs);
10940
10941 if (NS_SUCCEEDED(rv)) {
10942 modDate = msecs * int64_t(PR_USEC_PER_MSEC);
10943 }
10944 }
10945 } else {
10946 nsAutoCString contentDisp;
10947 rv = aChannel->GetContentDispositionHeader(contentDisp);
10948 if (NS_SUCCEEDED(rv)) {
10949 SetHeaderData(nsGkAtoms::headerContentDisposition,
10950 NS_ConvertASCIItoUTF16(contentDisp));
10951 }
10952 }
10953 }
10954
10955 mLastModified.Truncate();
10956 if (modDate != 0) {
10957 GetFormattedTimeString(modDate, mLastModified);
10958 }
10959 }
10960
ProcessMETATag(HTMLMetaElement * aMetaElement)10961 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) {
10962 // set any HTTP-EQUIV data into document's header data as well as url
10963 nsAutoString header;
10964 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header);
10965 if (!header.IsEmpty()) {
10966 // Ignore META REFRESH when document is sandboxed from automatic features.
10967 nsContentUtils::ASCIIToLower(header);
10968 if (nsGkAtoms::refresh->Equals(header) &&
10969 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
10970 return;
10971 }
10972
10973 nsAutoString result;
10974 aMetaElement->GetAttr(nsGkAtoms::content, result);
10975 if (!result.IsEmpty()) {
10976 RefPtr<nsAtom> fieldAtom(NS_Atomize(header));
10977 SetHeaderData(fieldAtom, result);
10978 }
10979 }
10980
10981 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
10982 nsGkAtoms::handheldFriendly, eIgnoreCase)) {
10983 nsAutoString result;
10984 aMetaElement->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result);
10985 if (!result.IsEmpty()) {
10986 nsContentUtils::ASCIIToLower(result);
10987 SetHeaderData(nsGkAtoms::handheldFriendly, result);
10988 }
10989 }
10990 }
10991
CreateElem(const nsAString & aName,nsAtom * aPrefix,int32_t aNamespaceID,const nsAString * aIs)10992 already_AddRefed<Element> Document::CreateElem(const nsAString& aName,
10993 nsAtom* aPrefix,
10994 int32_t aNamespaceID,
10995 const nsAString* aIs) {
10996 #ifdef DEBUG
10997 nsAutoString qName;
10998 if (aPrefix) {
10999 aPrefix->ToString(qName);
11000 qName.Append(':');
11001 }
11002 qName.Append(aName);
11003
11004 // Note: "a:b:c" is a valid name in non-namespaces XML, and
11005 // Document::CreateElement can call us with such a name and no prefix,
11006 // which would cause an error if we just used true here.
11007 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID();
11008 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)),
11009 "Don't pass invalid prefixes to Document::CreateElem, "
11010 "check caller.");
11011 #endif
11012
11013 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
11014 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE,
11015 getter_AddRefs(nodeInfo));
11016 NS_ENSURE_TRUE(nodeInfo, nullptr);
11017
11018 nsCOMPtr<Element> element;
11019 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
11020 NOT_FROM_PARSER, aIs);
11021 return NS_SUCCEEDED(rv) ? element.forget() : nullptr;
11022 }
11023
IsSafeToFlush() const11024 bool Document::IsSafeToFlush() const {
11025 PresShell* presShell = GetPresShell();
11026 if (!presShell) {
11027 return true;
11028 }
11029 return presShell->IsSafeToFlush();
11030 }
11031
Sanitize()11032 void Document::Sanitize() {
11033 // Sanitize the document by resetting all (current and former) password fields
11034 // and any form fields with autocomplete=off to their default values. We do
11035 // this now, instead of when the presentation is restored, to offer some
11036 // protection in case there is ever an exploit that allows a cached document
11037 // to be accessed from a different document.
11038
11039 // First locate all input elements, regardless of whether they are
11040 // in a form, and reset the password and autocomplete=off elements.
11041
11042 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns);
11043
11044 nsAutoString value;
11045
11046 uint32_t length = nodes->Length(true);
11047 for (uint32_t i = 0; i < length; ++i) {
11048 NS_ASSERTION(nodes->Item(i), "null item in node list!");
11049
11050 RefPtr<HTMLInputElement> input =
11051 HTMLInputElement::FromNodeOrNull(nodes->Item(i));
11052 if (!input) continue;
11053
11054 input->GetAttr(nsGkAtoms::autocomplete, value);
11055 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) {
11056 input->Reset();
11057 }
11058 }
11059
11060 // Now locate all _form_ elements that have autocomplete=off and reset them
11061 nodes = GetElementsByTagName(u"form"_ns);
11062
11063 length = nodes->Length(true);
11064 for (uint32_t i = 0; i < length; ++i) {
11065 // Reset() may change the list dynamically.
11066 RefPtr<HTMLFormElement> form =
11067 HTMLFormElement::FromNodeOrNull(nodes->Item(i));
11068 if (!form) continue;
11069
11070 form->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, value);
11071 if (value.LowerCaseEqualsLiteral("off")) form->Reset();
11072 }
11073 }
11074
EnumerateSubDocuments(SubDocEnumFunc aCallback)11075 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) {
11076 if (!mSubDocuments) {
11077 return;
11078 }
11079
11080 // PLDHashTable::Iterator can't handle modifications while iterating so we
11081 // copy all entries to an array first before calling any callbacks.
11082 AutoTArray<RefPtr<Document>, 8> subdocs;
11083 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11084 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11085 if (Document* subdoc = entry->mSubDocument) {
11086 subdocs.AppendElement(subdoc);
11087 }
11088 }
11089 for (auto& subdoc : subdocs) {
11090 if (aCallback(*subdoc) == CallState::Stop) {
11091 break;
11092 }
11093 }
11094 }
11095
CollectDescendantDocuments(nsTArray<RefPtr<Document>> & aDescendants,nsDocTestFunc aCallback) const11096 void Document::CollectDescendantDocuments(
11097 nsTArray<RefPtr<Document>>& aDescendants, nsDocTestFunc aCallback) const {
11098 if (!mSubDocuments) {
11099 return;
11100 }
11101
11102 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11103 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11104 const Document* subdoc = entry->mSubDocument;
11105 if (subdoc) {
11106 if (aCallback(subdoc)) {
11107 aDescendants.AppendElement(entry->mSubDocument);
11108 }
11109 subdoc->CollectDescendantDocuments(aDescendants, aCallback);
11110 }
11111 }
11112 }
11113
CanSavePresentation(nsIRequest * aNewRequest,uint32_t & aBFCacheCombo,bool aIncludeSubdocuments,bool aAllowUnloadListeners)11114 bool Document::CanSavePresentation(nsIRequest* aNewRequest,
11115 uint32_t& aBFCacheCombo,
11116 bool aIncludeSubdocuments,
11117 bool aAllowUnloadListeners) {
11118 bool ret = true;
11119
11120 if (!IsBFCachingAllowed()) {
11121 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED;
11122 ret = false;
11123 }
11124
11125 nsAutoCString uri;
11126 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11127 if (mDocumentURI) {
11128 mDocumentURI->GetSpec(uri);
11129 }
11130 }
11131
11132 if (EventHandlingSuppressed()) {
11133 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11134 ("Save of %s blocked on event handling suppression", uri.get()));
11135 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED;
11136 ret = false;
11137 }
11138
11139 // Do not allow suspended windows to be placed in the
11140 // bfcache. This method is also used to verify a document
11141 // coming out of the bfcache is ok to restore, though. So
11142 // we only want to block suspend windows that aren't also
11143 // frozen.
11144 nsPIDOMWindowInner* win = GetInnerWindow();
11145 if (win && win->IsSuspended() && !win->IsFrozen()) {
11146 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11147 ("Save of %s blocked on suspended Window", uri.get()));
11148 aBFCacheCombo |= BFCacheStatus::SUSPENDED;
11149 ret = false;
11150 }
11151
11152 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest);
11153 bool thirdParty = false;
11154 // Currently some other mobile browsers seem to bfcache only cross-domain
11155 // pages, but bfcache those also when there are unload event listeners, so
11156 // this is trying to match that behavior as much as possible.
11157 bool allowUnloadListeners =
11158 aAllowUnloadListeners &&
11159 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() &&
11160 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel(
11161 channel, &thirdParty)) &&
11162 thirdParty));
11163
11164 // Check our event listener manager for unload/beforeunload listeners.
11165 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
11166 if (!allowUnloadListeners && piTarget) {
11167 EventListenerManager* manager = piTarget->GetExistingListenerManager();
11168 if (manager) {
11169 if (manager->HasUnloadListeners()) {
11170 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11171 ("Save of %s blocked due to unload handlers", uri.get()));
11172 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER;
11173 ret = false;
11174 }
11175 if (manager->HasBeforeUnloadListeners()) {
11176 if (!mozilla::SessionHistoryInParent() ||
11177 !StaticPrefs::
11178 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) {
11179 MOZ_LOG(
11180 gPageCacheLog, mozilla::LogLevel::Verbose,
11181 ("Save of %s blocked due to beforeUnload handlers", uri.get()));
11182 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER;
11183 ret = false;
11184 }
11185 }
11186 }
11187 }
11188
11189 // Check if we have pending network requests
11190 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11191 if (loadGroup) {
11192 nsCOMPtr<nsISimpleEnumerator> requests;
11193 loadGroup->GetRequests(getter_AddRefs(requests));
11194
11195 bool hasMore = false;
11196
11197 // We want to bail out if we have any requests other than aNewRequest (or
11198 // in the case when aNewRequest is a part of a multipart response the base
11199 // channel the multipart response is coming in on).
11200 nsCOMPtr<nsIChannel> baseChannel;
11201 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest));
11202 if (part) {
11203 part->GetBaseChannel(getter_AddRefs(baseChannel));
11204 }
11205
11206 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11207 nsCOMPtr<nsISupports> elem;
11208 requests->GetNext(getter_AddRefs(elem));
11209
11210 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11211 if (request && request != aNewRequest && request != baseChannel) {
11212 // Favicon loads don't need to block caching.
11213 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
11214 if (channel) {
11215 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
11216 if (li->InternalContentPolicyType() ==
11217 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
11218 continue;
11219 }
11220 }
11221
11222 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
11223 nsAutoCString requestName;
11224 request->GetName(requestName);
11225 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
11226 ("Save of %s blocked because document has request %s",
11227 uri.get(), requestName.get()));
11228 }
11229 aBFCacheCombo |= BFCacheStatus::REQUEST;
11230 ret = false;
11231 }
11232 }
11233 }
11234
11235 // Check if we have active GetUserMedia use
11236 if (MediaManager::Exists() && win &&
11237 MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
11238 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11239 ("Save of %s blocked due to GetUserMedia", uri.get()));
11240 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA;
11241 ret = false;
11242 }
11243
11244 #ifdef MOZ_WEBRTC
11245 // Check if we have active PeerConnections
11246 if (win && win->HasActivePeerConnections()) {
11247 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11248 ("Save of %s blocked due to PeerConnection", uri.get()));
11249 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION;
11250 ret = false;
11251 }
11252 #endif // MOZ_WEBRTC
11253
11254 // Don't save presentations for documents containing EME content, so that
11255 // CDMs reliably shutdown upon user navigation.
11256 if (ContainsEMEContent()) {
11257 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT;
11258 ret = false;
11259 }
11260
11261 // Don't save presentations for documents containing MSE content, to
11262 // reduce memory usage.
11263 if (ContainsMSEContent()) {
11264 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11265 ("Save of %s blocked due to MSE use", uri.get()));
11266 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT;
11267 ret = false;
11268 }
11269
11270 if (aIncludeSubdocuments && mSubDocuments) {
11271 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
11272 auto entry = static_cast<SubDocMapEntry*>(iter.Get());
11273 Document* subdoc = entry->mSubDocument;
11274
11275 uint32_t subDocBFCacheCombo = 0;
11276 // The aIgnoreRequest we were passed is only for us, so don't pass it on.
11277 bool canCache =
11278 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo,
11279 true, allowUnloadListeners)
11280 : false;
11281 if (!canCache) {
11282 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11283 ("Save of %s blocked due to subdocument blocked", uri.get()));
11284 aBFCacheCombo |= subDocBFCacheCombo;
11285 ret = false;
11286 }
11287 }
11288 }
11289
11290 if (!mozilla::BFCacheInParent()) {
11291 // BFCache is currently not compatible with remote subframes (bug 1609324)
11292 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) {
11293 for (auto& child : browsingContext->Children()) {
11294 if (!child->IsInProcess()) {
11295 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES;
11296 ret = false;
11297 break;
11298 }
11299 }
11300 }
11301 }
11302
11303 if (win) {
11304 auto* globalWindow = nsGlobalWindowInner::Cast(win);
11305 #ifdef MOZ_WEBSPEECH
11306 if (globalWindow->HasActiveSpeechSynthesis()) {
11307 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11308 ("Save of %s blocked due to Speech use", uri.get()));
11309 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS;
11310 ret = false;
11311 }
11312 #endif
11313 if (globalWindow->HasUsedVR()) {
11314 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
11315 ("Save of %s blocked due to having used VR", uri.get()));
11316 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR;
11317 ret = false;
11318 }
11319
11320 if (win->HasActiveLocks()) {
11321 MOZ_LOG(
11322 gPageCacheLog, mozilla::LogLevel::Verbose,
11323 ("Save of %s blocked due to having active lock requests", uri.get()));
11324 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK;
11325 ret = false;
11326 }
11327 }
11328
11329 return ret;
11330 }
11331
Destroy()11332 void Document::Destroy() {
11333 // The ContentViewer wants to release the document now. So, tell our content
11334 // to drop any references to the document so that it can be destroyed.
11335 if (mIsGoingAway) {
11336 return;
11337 }
11338
11339 ReportDocumentUseCounters();
11340 SetDevToolsWatchingDOMMutations(false);
11341
11342 mIsGoingAway = true;
11343
11344 ScriptLoader()->Destroy();
11345 SetScriptGlobalObject(nullptr);
11346 RemovedFromDocShell();
11347
11348 bool oldVal = mInUnlinkOrDeletion;
11349 mInUnlinkOrDeletion = true;
11350
11351 #ifdef DEBUG
11352 uint32_t oldChildCount = GetChildCount();
11353 #endif
11354
11355 for (nsIContent* child = GetFirstChild(); child;
11356 child = child->GetNextSibling()) {
11357 child->DestroyContent();
11358 MOZ_ASSERT(child->GetParentNode() == this);
11359 }
11360 MOZ_ASSERT(oldChildCount == GetChildCount());
11361 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0);
11362
11363 mInUnlinkOrDeletion = oldVal;
11364
11365 mLayoutHistoryState = nullptr;
11366
11367 if (mOriginalDocument) {
11368 mOriginalDocument->mLatestStaticClone = nullptr;
11369 }
11370
11371 if (IsStaticDocument()) {
11372 RemoveProperty(nsGkAtoms::printselectionranges);
11373 }
11374
11375 // Shut down our external resource map. We might not need this for
11376 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but
11377 // tearing down all those frame trees right now is the right thing to do.
11378 mExternalResourceMap.Shutdown();
11379
11380 // Manually break cycles via promise's global object pointer.
11381 mReadyForIdle = nullptr;
11382 mOrientationPendingPromise = nullptr;
11383
11384 // To break cycles.
11385 mPreloadService.ClearAllPreloads();
11386
11387 if (mDocumentL10n) {
11388 mDocumentL10n->Destroy();
11389 }
11390 }
11391
RemovedFromDocShell()11392 void Document::RemovedFromDocShell() {
11393 mEditingState = EditingState::eOff;
11394
11395 if (mRemovedFromDocShell) return;
11396
11397 mRemovedFromDocShell = true;
11398 NotifyActivityChanged();
11399
11400 for (nsIContent* child = GetFirstChild(); child;
11401 child = child->GetNextSibling()) {
11402 child->SaveSubtreeState();
11403 }
11404
11405 nsIDocShell* docShell = GetDocShell();
11406 if (docShell) {
11407 docShell->SynchronizeLayoutHistoryState();
11408 }
11409 }
11410
GetLayoutHistoryState() const11411 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState()
11412 const {
11413 nsCOMPtr<nsILayoutHistoryState> state;
11414 if (!mScriptGlobalObject) {
11415 state = mLayoutHistoryState;
11416 } else {
11417 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
11418 if (docShell) {
11419 docShell->GetLayoutHistoryState(getter_AddRefs(state));
11420 }
11421 }
11422
11423 return state.forget();
11424 }
11425
EnsureOnloadBlocker()11426 void Document::EnsureOnloadBlocker() {
11427 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11428 // -- it's not ours.
11429 if (mOnloadBlockCount != 0 && mScriptGlobalObject) {
11430 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
11431 if (loadGroup) {
11432 // Check first to see if mOnloadBlocker is in the loadgroup.
11433 nsCOMPtr<nsISimpleEnumerator> requests;
11434 loadGroup->GetRequests(getter_AddRefs(requests));
11435
11436 bool hasMore = false;
11437 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
11438 nsCOMPtr<nsISupports> elem;
11439 requests->GetNext(getter_AddRefs(elem));
11440 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem);
11441 if (request && request == mOnloadBlocker) {
11442 return;
11443 }
11444 }
11445
11446 // Not in the loadgroup, so add it.
11447 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11448 }
11449 }
11450 }
11451
BlockOnload()11452 void Document::BlockOnload() {
11453 if (mDisplayDocument) {
11454 mDisplayDocument->BlockOnload();
11455 return;
11456 }
11457
11458 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11459 // -- it's not ours.
11460 if (mOnloadBlockCount == 0 && mScriptGlobalObject) {
11461 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11462 loadGroup->AddRequest(mOnloadBlocker, nullptr);
11463 }
11464 }
11465 ++mOnloadBlockCount;
11466 }
11467
UnblockOnload(bool aFireSync)11468 void Document::UnblockOnload(bool aFireSync) {
11469 if (mDisplayDocument) {
11470 mDisplayDocument->UnblockOnload(aFireSync);
11471 return;
11472 }
11473
11474 --mOnloadBlockCount;
11475
11476 if (mOnloadBlockCount == 0) {
11477 if (mScriptGlobalObject) {
11478 // Only manipulate the loadgroup in this case, because if
11479 // mScriptGlobalObject is null, it's not ours.
11480 if (aFireSync) {
11481 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it
11482 ++mOnloadBlockCount;
11483 DoUnblockOnload();
11484 } else {
11485 PostUnblockOnloadEvent();
11486 }
11487 } else if (mIsBeingUsedAsImage) {
11488 // To correctly unblock onload for a document that contains an SVG
11489 // image, we need to know when all of the SVG document's resources are
11490 // done loading, in a way comparable to |window.onload|. We fire this
11491 // event to indicate that the SVG should be considered fully loaded.
11492 // Because scripting is disabled on SVG-as-image documents, this event
11493 // is not accessible to content authors. (See bug 837315.)
11494 RefPtr<AsyncEventDispatcher> asyncDispatcher =
11495 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns,
11496 CanBubble::eNo, ChromeOnlyDispatch::eNo);
11497 asyncDispatcher->PostDOMEvent();
11498 }
11499 }
11500 }
11501
11502 class nsUnblockOnloadEvent : public Runnable {
11503 public:
nsUnblockOnloadEvent(Document * aDoc)11504 explicit nsUnblockOnloadEvent(Document* aDoc)
11505 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {}
Run()11506 NS_IMETHOD Run() override {
11507 mDoc->DoUnblockOnload();
11508 return NS_OK;
11509 }
11510
11511 private:
11512 RefPtr<Document> mDoc;
11513 };
11514
PostUnblockOnloadEvent()11515 void Document::PostUnblockOnloadEvent() {
11516 MOZ_RELEASE_ASSERT(NS_IsMainThread());
11517 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
11518 nsresult rv = Dispatch(TaskCategory::Other, evt.forget());
11519 if (NS_SUCCEEDED(rv)) {
11520 // Stabilize block count so we don't post more events while this one is up
11521 ++mOnloadBlockCount;
11522 } else {
11523 NS_WARNING("failed to dispatch nsUnblockOnloadEvent");
11524 }
11525 }
11526
DoUnblockOnload()11527 void Document::DoUnblockOnload() {
11528 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document");
11529 MOZ_ASSERT(mOnloadBlockCount != 0,
11530 "Shouldn't have a count of zero here, since we stabilized in "
11531 "PostUnblockOnloadEvent");
11532
11533 --mOnloadBlockCount;
11534
11535 if (mOnloadBlockCount != 0) {
11536 // We blocked again after the last unblock. Nothing to do here. We'll
11537 // post a new event when we unblock again.
11538 return;
11539 }
11540
11541 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
11542 // -- it's not ours.
11543 if (mScriptGlobalObject) {
11544 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) {
11545 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
11546 }
11547 }
11548 }
11549
GetContentInThisDocument(nsIFrame * aFrame) const11550 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const {
11551 for (nsIFrame* f = aFrame; f;
11552 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
11553 nsIContent* content = f->GetContent();
11554 if (!content || content->IsInNativeAnonymousSubtree()) continue;
11555
11556 if (content->OwnerDoc() == this) {
11557 return content;
11558 }
11559 // We must be in a subdocument so jump directly to the root frame.
11560 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to
11561 // the containing document.
11562 f = f->PresContext()->GetPresShell()->GetRootFrame();
11563 }
11564
11565 return nullptr;
11566 }
11567
DispatchPageTransition(EventTarget * aDispatchTarget,const nsAString & aType,bool aInFrameSwap,bool aPersisted,bool aOnlySystemGroup)11568 void Document::DispatchPageTransition(EventTarget* aDispatchTarget,
11569 const nsAString& aType, bool aInFrameSwap,
11570 bool aPersisted, bool aOnlySystemGroup) {
11571 if (!aDispatchTarget) {
11572 return;
11573 }
11574
11575 PageTransitionEventInit init;
11576 init.mBubbles = true;
11577 init.mCancelable = true;
11578 init.mPersisted = aPersisted;
11579 init.mInFrameSwap = aInFrameSwap;
11580
11581 RefPtr<PageTransitionEvent> event =
11582 PageTransitionEvent::Constructor(this, aType, init);
11583
11584 event->SetTrusted(true);
11585 event->SetTarget(this);
11586 if (aOnlySystemGroup) {
11587 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true;
11588 }
11589 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr,
11590 nullptr);
11591 }
11592
OnPageShow(bool aPersisted,EventTarget * aDispatchStartTarget,bool aOnlySystemGroup)11593 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget,
11594 bool aOnlySystemGroup) {
11595 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11596 nsCString uri;
11597 if (GetDocumentURI()) {
11598 uri = GetDocumentURI()->GetSpecOrDefault();
11599 }
11600 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11601 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted));
11602 }
11603
11604 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11605 MOZ_DIAGNOSTIC_ASSERT(
11606 inFrameLoaderSwap ==
11607 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11608
11609 Element* root = GetRootElement();
11610 if (aPersisted && root) {
11611 // Send out notifications that our <link> elements are attached.
11612 RefPtr<nsContentList> links =
11613 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11614
11615 uint32_t linkCount = links->Length(true);
11616 for (uint32_t i = 0; i < linkCount; ++i) {
11617 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
11618 }
11619 }
11620
11621 // See Document
11622 if (!inFrameLoaderSwap) {
11623 if (aPersisted) {
11624 ImageTracker()->SetAnimatingState(true);
11625 }
11626
11627 // Set mIsShowing before firing events, in case those event handlers
11628 // move us around.
11629 mIsShowing = true;
11630 mVisible = true;
11631
11632 UpdateVisibilityState();
11633 }
11634
11635 NotifyActivityChanged();
11636
11637 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11638 aExternalResource.OnPageShow(aPersisted, nullptr);
11639 return CallState::Continue;
11640 };
11641 EnumerateExternalResources(notifyExternal);
11642
11643 if (mAnimationController) {
11644 mAnimationController->OnPageShow();
11645 }
11646
11647 if (!mIsBeingUsedAsImage) {
11648 // Dispatch observer notification to notify observers page is shown.
11649 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11650 if (os) {
11651 nsIPrincipal* principal = NodePrincipal();
11652 os->NotifyObservers(ToSupports(this),
11653 principal->IsSystemPrincipal() ? "chrome-page-shown"
11654 : "content-page-shown",
11655 nullptr);
11656 }
11657
11658 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11659 if (!target) {
11660 target = do_QueryInterface(GetWindow());
11661 }
11662 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap,
11663 aPersisted, aOnlySystemGroup);
11664 }
11665 }
11666
DispatchFullscreenChange(Document & aDocument,nsINode * aTarget)11667 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) {
11668 if (nsPresContext* presContext = aDocument.GetPresContext()) {
11669 auto pendingEvent = MakeUnique<PendingFullscreenEvent>(
11670 FullscreenEventType::Change, &aDocument, aTarget);
11671 presContext->RefreshDriver()->ScheduleFullscreenEvent(
11672 std::move(pendingEvent));
11673 }
11674 }
11675
11676 static void ClearPendingFullscreenRequests(Document* aDoc);
11677
OnPageHide(bool aPersisted,EventTarget * aDispatchStartTarget,bool aOnlySystemGroup)11678 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget,
11679 bool aOnlySystemGroup) {
11680 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) {
11681 nsCString uri;
11682 if (GetDocumentURI()) {
11683 uri = GetDocumentURI()->GetSpecOrDefault();
11684 }
11685 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
11686 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted));
11687 }
11688
11689 const bool inFrameLoaderSwap = !!aDispatchStartTarget;
11690 MOZ_DIAGNOSTIC_ASSERT(
11691 inFrameLoaderSwap ==
11692 (mDocumentContainer && mDocumentContainer->InFrameSwap()));
11693
11694 // Send out notifications that our <link> elements are detached,
11695 // but only if this is not a full unload.
11696 Element* root = GetRootElement();
11697 if (aPersisted && root) {
11698 RefPtr<nsContentList> links =
11699 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns);
11700
11701 uint32_t linkCount = links->Length(true);
11702 for (uint32_t i = 0; i < linkCount; ++i) {
11703 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
11704 }
11705 }
11706
11707 if (mAnimationController) {
11708 mAnimationController->OnPageHide();
11709 }
11710
11711 if (!inFrameLoaderSwap) {
11712 if (aPersisted) {
11713 // We do not stop the animations (bug 1024343) when the page is refreshing
11714 // while being dragged out.
11715 ImageTracker()->SetAnimatingState(false);
11716 }
11717
11718 // Set mIsShowing before firing events, in case those event handlers
11719 // move us around.
11720 mIsShowing = false;
11721 mVisible = false;
11722 }
11723
11724 ExitPointerLock();
11725
11726 if (!mIsBeingUsedAsImage) {
11727 // Dispatch observer notification to notify observers page is hidden.
11728 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
11729 if (os) {
11730 nsIPrincipal* principal = NodePrincipal();
11731 os->NotifyObservers(ToSupports(this),
11732 principal->IsSystemPrincipal()
11733 ? "chrome-page-hidden"
11734 : "content-page-hidden",
11735 nullptr);
11736 }
11737
11738 // Now send out a PageHide event.
11739 nsCOMPtr<EventTarget> target = aDispatchStartTarget;
11740 if (!target) {
11741 target = do_QueryInterface(GetWindow());
11742 }
11743 {
11744 PageUnloadingEventTimeStamp timeStamp(this);
11745 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap,
11746 aPersisted, aOnlySystemGroup);
11747 }
11748 }
11749
11750 if (!inFrameLoaderSwap) {
11751 UpdateVisibilityState();
11752 }
11753
11754 auto notifyExternal = [aPersisted](Document& aExternalResource) {
11755 aExternalResource.OnPageHide(aPersisted, nullptr);
11756 return CallState::Continue;
11757 };
11758 EnumerateExternalResources(notifyExternal);
11759 NotifyActivityChanged();
11760
11761 ClearPendingFullscreenRequests(this);
11762 if (GetUnretargetedFullScreenElement()) {
11763 // If this document was fullscreen, we should exit fullscreen in this
11764 // doctree branch. This ensures that if the user navigates while in
11765 // fullscreen mode we don't leave its still visible ancestor documents
11766 // in fullscreen mode. So exit fullscreen in the document's fullscreen
11767 // root document, as this will exit fullscreen in all the root's
11768 // descendant documents. Note that documents are removed from the
11769 // doctree by the time OnPageHide() is called, so we must store a
11770 // reference to the root (in Document::mFullscreenRoot) since we can't
11771 // just traverse the doctree to get the root.
11772 Document::ExitFullscreenInDocTree(this);
11773
11774 // Since the document is removed from the doctree before OnPageHide() is
11775 // called, ExitFullscreen() can't traverse from the root down to *this*
11776 // document, so we must manually call CleanupFullscreenState() below too.
11777 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot,
11778 // so we *must* call it after ExitFullscreen(), not before.
11779 // OnPageHide() is called in every hidden (i.e. descendant) document,
11780 // so calling CleanupFullscreenState() here will ensure all hidden
11781 // documents have their fullscreen state reset.
11782 CleanupFullscreenState();
11783
11784 // The fullscreenchange event is to be queued in the refresh driver,
11785 // however a hidden page wouldn't trigger that again, so it makes no
11786 // sense to dispatch such event here.
11787 }
11788 }
11789
WillDispatchMutationEvent(nsINode * aTarget)11790 void Document::WillDispatchMutationEvent(nsINode* aTarget) {
11791 NS_ASSERTION(
11792 mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0,
11793 "mSubtreeModifiedTargets not cleared after dispatching?");
11794 ++mSubtreeModifiedDepth;
11795 if (aTarget) {
11796 // MayDispatchMutationEvent is often called just before this method,
11797 // so it has already appended the node to mSubtreeModifiedTargets.
11798 int32_t count = mSubtreeModifiedTargets.Count();
11799 if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) {
11800 mSubtreeModifiedTargets.AppendObject(aTarget);
11801 }
11802 }
11803 }
11804
MutationEventDispatched(nsINode * aTarget)11805 void Document::MutationEventDispatched(nsINode* aTarget) {
11806 --mSubtreeModifiedDepth;
11807 if (mSubtreeModifiedDepth == 0) {
11808 int32_t count = mSubtreeModifiedTargets.Count();
11809 if (!count) {
11810 return;
11811 }
11812
11813 nsPIDOMWindowInner* window = GetInnerWindow();
11814 if (window &&
11815 !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
11816 mSubtreeModifiedTargets.Clear();
11817 return;
11818 }
11819
11820 nsCOMArray<nsINode> realTargets;
11821 for (int32_t i = 0; i < count; ++i) {
11822 nsINode* possibleTarget = mSubtreeModifiedTargets[i];
11823 if (possibleTarget && possibleTarget->ChromeOnlyAccess()) {
11824 continue;
11825 }
11826
11827 nsINode* commonAncestor = nullptr;
11828 int32_t realTargetCount = realTargets.Count();
11829 for (int32_t j = 0; j < realTargetCount; ++j) {
11830 commonAncestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
11831 possibleTarget, realTargets[j]);
11832 if (commonAncestor) {
11833 realTargets.ReplaceObjectAt(commonAncestor, j);
11834 break;
11835 }
11836 }
11837 if (!commonAncestor) {
11838 realTargets.AppendObject(possibleTarget);
11839 }
11840 }
11841
11842 mSubtreeModifiedTargets.Clear();
11843
11844 int32_t realTargetCount = realTargets.Count();
11845 for (int32_t k = 0; k < realTargetCount; ++k) {
11846 InternalMutationEvent mutation(true, eLegacySubtreeModified);
11847 (new AsyncEventDispatcher(realTargets[k], mutation))
11848 ->RunDOMEventWhenSafe();
11849 }
11850 }
11851 }
11852
DestroyElementMaps()11853 void Document::DestroyElementMaps() {
11854 #ifdef DEBUG
11855 mStyledLinksCleared = true;
11856 #endif
11857 mStyledLinks.Clear();
11858 // Notify ID change listeners before clearing the identifier map.
11859 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
11860 iter.Get()->ClearAndNotify();
11861 }
11862 mIdentifierMap.Clear();
11863 mComposedShadowRoots.Clear();
11864 mResponsiveContent.Clear();
11865 IncrementExpandoGeneration(*this);
11866 }
11867
RefreshLinkHrefs()11868 void Document::RefreshLinkHrefs() {
11869 // Get a list of all links we know about. We will reset them, which will
11870 // remove them from the document, so we need a copy of what is in the
11871 // hashtable.
11872 const LinkArray linksToNotify = ToArray(mStyledLinks);
11873
11874 // Reset all of our styled links.
11875 nsAutoScriptBlocker scriptBlocker;
11876 for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) {
11877 linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref());
11878 }
11879 }
11880
CloneDocHelper(Document * clone) const11881 nsresult Document::CloneDocHelper(Document* clone) const {
11882 clone->mIsStaticDocument = mCreatingStaticClone;
11883
11884 // Init document
11885 nsresult rv = clone->Init();
11886 NS_ENSURE_SUCCESS(rv, rv);
11887
11888 if (mCreatingStaticClone) {
11889 if (mOriginalDocument) {
11890 clone->mOriginalDocument = mOriginalDocument;
11891 } else {
11892 clone->mOriginalDocument = const_cast<Document*>(this);
11893 }
11894 clone->mOriginalDocument->mLatestStaticClone = clone;
11895 clone->mOriginalDocument->mStaticCloneCount++;
11896
11897 nsCOMPtr<nsILoadGroup> loadGroup;
11898
11899 // |mDocumentContainer| is the container of the document that is being
11900 // created and not the original container. See CreateStaticClone function().
11901 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer);
11902 if (docLoader) {
11903 docLoader->GetLoadGroup(getter_AddRefs(loadGroup));
11904 }
11905 nsCOMPtr<nsIChannel> channel = GetChannel();
11906 nsCOMPtr<nsIURI> uri;
11907 if (channel) {
11908 NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
11909 } else {
11910 uri = Document::GetDocumentURI();
11911 }
11912 clone->mChannel = channel;
11913 if (uri) {
11914 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal);
11915 }
11916
11917 clone->mIsSrcdocDocument = mIsSrcdocDocument;
11918 clone->SetContainer(mDocumentContainer);
11919
11920 // Setup the navigation time. This will be needed by any animations in the
11921 // document, even if they are only paused.
11922 MOZ_ASSERT(!clone->GetNavigationTiming(),
11923 "Navigation time was already set?");
11924 if (mTiming) {
11925 RefPtr<nsDOMNavigationTiming> timing =
11926 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell()));
11927 clone->SetNavigationTiming(timing);
11928 }
11929 clone->SetCsp(mCSP);
11930 }
11931
11932 // Now ensure that our clone has the same URI, base URI, and principal as us.
11933 // We do this after the mCreatingStaticClone block above, because that block
11934 // can set the base URI to an incorrect value in cases when base URI
11935 // information came from the channel. So we override explicitly, and do it
11936 // for all these properties, in case ResetToURI messes with any of the rest of
11937 // them.
11938 clone->SetDocumentURI(Document::GetDocumentURI());
11939 clone->SetChromeXHRDocURI(mChromeXHRDocURI);
11940 clone->SetPrincipals(NodePrincipal(), mPartitionedPrincipal);
11941 clone->mActiveStoragePrincipal = mActiveStoragePrincipal;
11942 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than
11943 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even
11944 // when printed standalone via window.print() (where there won't be a parent
11945 // document to grab the URI from).
11946 clone->mDocumentBaseURI = GetDocBaseURI();
11947 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
11948 clone->mReferrerInfo =
11949 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
11950 clone->mPreloadReferrerInfo = clone->mReferrerInfo;
11951
11952 bool hasHadScriptObject = true;
11953 nsIScriptGlobalObject* scriptObject =
11954 GetScriptHandlingObject(hasHadScriptObject);
11955 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
11956 if (mCreatingStaticClone) {
11957 // If we're doing a static clone (print, print preview), then we're going to
11958 // be setting a scope object after the clone. It's better to set it only
11959 // once, so we don't do that here. However, we do want to act as if there is
11960 // a script handling object. So we set mHasHadScriptHandlingObject.
11961 clone->mHasHadScriptHandlingObject = true;
11962 } else if (scriptObject) {
11963 clone->SetScriptHandlingObject(scriptObject);
11964 } else {
11965 clone->SetScopeObject(GetScopeObject());
11966 }
11967 // Make the clone a data document
11968 clone->SetLoadedAsData(
11969 true,
11970 /* aConsiderForMemoryReporting */ !mCreatingStaticClone);
11971
11972 // Misc state
11973
11974 // State from Document
11975 clone->mCharacterSet = mCharacterSet;
11976 clone->mCharacterSetSource = mCharacterSetSource;
11977 clone->SetCompatibilityMode(mCompatMode);
11978 clone->mBidiOptions = mBidiOptions;
11979 clone->mContentLanguage = mContentLanguage;
11980 clone->SetContentType(GetContentTypeInternal());
11981 clone->mSecurityInfo = mSecurityInfo;
11982
11983 // State from Document
11984 clone->mType = mType;
11985 clone->mXMLDeclarationBits = mXMLDeclarationBits;
11986 clone->mBaseTarget = mBaseTarget;
11987
11988 return NS_OK;
11989 }
11990
NotifyLoading(bool aNewParentIsLoading,const ReadyState & aCurrentState,ReadyState aNewState)11991 void Document::NotifyLoading(bool aNewParentIsLoading,
11992 const ReadyState& aCurrentState,
11993 ReadyState aNewState) {
11994 // Mirror the top-level loading state down to all subdocuments
11995 bool was_loading = mAncestorIsLoading ||
11996 aCurrentState == READYSTATE_LOADING ||
11997 aCurrentState == READYSTATE_INTERACTIVE;
11998 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING ||
11999 aNewState == READYSTATE_INTERACTIVE; // new value for state
12000 bool set_load_state = was_loading != is_loading;
12001
12002 MOZ_LOG(
12003 gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12004 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, "
12005 "currentState %d newState: %d, was_loading: %d, is_loading: %d, "
12006 "set_load_state: %d",
12007 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState,
12008 (int)aNewState, was_loading, is_loading, set_load_state));
12009
12010 mAncestorIsLoading = aNewParentIsLoading;
12011 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load()) {
12012 // Tell our innerwindow (and thus TimeoutManager)
12013 nsPIDOMWindowInner* inner = GetInnerWindow();
12014 if (inner) {
12015 inner->SetActiveLoadingState(is_loading);
12016 }
12017 BrowsingContext* context = GetBrowsingContext();
12018 if (context) {
12019 // Don't use PreOrderWalk to mirror this down; go down one level as a
12020 // time so we can set mAncestorIsLoading and take into account the
12021 // readystates of the subdocument. In the child process it will call
12022 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then
12023 // iterate it's children
12024 for (auto& child : context->Children()) {
12025 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
12026 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading));
12027 // Setting ancestor loading on a discarded browsing context has no
12028 // effect.
12029 Unused << child->SetAncestorLoading(is_loading);
12030 }
12031 }
12032 }
12033 }
12034
SetReadyStateInternal(ReadyState aReadyState,bool aUpdateTimingInformation)12035 void Document::SetReadyStateInternal(ReadyState aReadyState,
12036 bool aUpdateTimingInformation) {
12037 if (aReadyState == READYSTATE_UNINITIALIZED) {
12038 // Transition back to uninitialized happens only to keep assertions happy
12039 // right before readyState transitions to something else. Make this
12040 // transition undetectable by Web content.
12041 mReadyState = aReadyState;
12042 return;
12043 }
12044
12045 if (IsTopLevelContentDocument()) {
12046 if (aReadyState == READYSTATE_LOADING) {
12047 AddToplevelLoadingDocument(this);
12048 } else if (aReadyState == READYSTATE_COMPLETE) {
12049 RemoveToplevelLoadingDocument(this);
12050 }
12051 }
12052
12053 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
12054 mLoadingTimeStamp = TimeStamp::Now();
12055 }
12056 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
12057 mReadyState = aReadyState;
12058 if (aUpdateTimingInformation && mTiming) {
12059 switch (aReadyState) {
12060 case READYSTATE_LOADING:
12061 mTiming->NotifyDOMLoading(GetDocumentURI());
12062 break;
12063 case READYSTATE_INTERACTIVE:
12064 mTiming->NotifyDOMInteractive(GetDocumentURI());
12065 break;
12066 case READYSTATE_COMPLETE:
12067 mTiming->NotifyDOMComplete(GetDocumentURI());
12068 break;
12069 default:
12070 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value");
12071 break;
12072 }
12073 }
12074 // At the time of loading start, we don't have timing object, record time.
12075
12076 if (READYSTATE_INTERACTIVE == aReadyState &&
12077 NodePrincipal()->IsSystemPrincipal()) {
12078 if (!mXULPersist) {
12079 mXULPersist = new XULPersist(this);
12080 mXULPersist->Init();
12081 }
12082 if (!mChromeObserver) {
12083 mChromeObserver = new ChromeObserver(this);
12084 mChromeObserver->Init();
12085 }
12086 }
12087
12088 if (aUpdateTimingInformation) {
12089 RecordNavigationTiming(aReadyState);
12090 }
12091
12092 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
12093 this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
12094 asyncDispatcher->RunDOMEventWhenSafe();
12095 }
12096
GetReadyState(nsAString & aReadyState) const12097 void Document::GetReadyState(nsAString& aReadyState) const {
12098 switch (mReadyState) {
12099 case READYSTATE_LOADING:
12100 aReadyState.AssignLiteral(u"loading");
12101 break;
12102 case READYSTATE_INTERACTIVE:
12103 aReadyState.AssignLiteral(u"interactive");
12104 break;
12105 case READYSTATE_COMPLETE:
12106 aReadyState.AssignLiteral(u"complete");
12107 break;
12108 default:
12109 aReadyState.AssignLiteral(u"uninitialized");
12110 }
12111 }
12112
SuppressEventHandling(uint32_t aIncrease)12113 void Document::SuppressEventHandling(uint32_t aIncrease) {
12114 mEventsSuppressed += aIncrease;
12115 if (mEventsSuppressed == aIncrease) {
12116 WindowGlobalChild* wgc = GetWindowGlobalChild();
12117 if (wgc) {
12118 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12119 }
12120 }
12121 UpdateFrameRequestCallbackSchedulingState();
12122 for (uint32_t i = 0; i < aIncrease; ++i) {
12123 ScriptLoader()->AddExecuteBlocker();
12124 }
12125
12126 auto suppressInSubDoc = [aIncrease](Document& aSubDoc) {
12127 aSubDoc.SuppressEventHandling(aIncrease);
12128 return CallState::Continue;
12129 };
12130
12131 EnumerateSubDocuments(suppressInSubDoc);
12132 }
12133
NotifyAbortedLoad()12134 void Document::NotifyAbortedLoad() {
12135 // If we still have outstanding work blocking DOMContentLoaded,
12136 // then don't try to change the readystate now, but wait until
12137 // they finish and then do so.
12138 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) {
12139 mSetCompleteAfterDOMContentLoaded = true;
12140 return;
12141 }
12142
12143 // Otherwise we're fully done at this point, so set the
12144 // readystate to complete.
12145 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) {
12146 SetReadyStateInternal(Document::READYSTATE_COMPLETE);
12147 }
12148 }
12149
FireOrClearDelayedEvents(nsTArray<nsCOMPtr<Document>> & aDocuments,bool aFireEvents)12150 static void FireOrClearDelayedEvents(nsTArray<nsCOMPtr<Document>>& aDocuments,
12151 bool aFireEvents) {
12152 nsFocusManager* fm = nsFocusManager::GetFocusManager();
12153 if (!fm) return;
12154
12155 for (uint32_t i = 0; i < aDocuments.Length(); ++i) {
12156 // NB: Don't bother trying to fire delayed events on documents that were
12157 // closed before this event ran.
12158 if (!aDocuments[i]->EventHandlingSuppressed()) {
12159 fm->FireDelayedEvents(aDocuments[i]);
12160 RefPtr<PresShell> presShell = aDocuments[i]->GetPresShell();
12161 if (presShell) {
12162 // Only fire events for active documents.
12163 bool fire = aFireEvents && aDocuments[i]->GetInnerWindow() &&
12164 aDocuments[i]->GetInnerWindow()->IsCurrentInnerWindow();
12165 presShell->FireOrClearDelayedEvents(fire);
12166 }
12167 aDocuments[i]->FireOrClearPostMessageEvents(aFireEvents);
12168 }
12169 }
12170 }
12171
PreloadPictureClosed()12172 void Document::PreloadPictureClosed() {
12173 MOZ_ASSERT(mPreloadPictureDepth > 0);
12174 mPreloadPictureDepth--;
12175 if (mPreloadPictureDepth == 0) {
12176 mPreloadPictureFoundSource.SetIsVoid(true);
12177 }
12178 }
12179
PreloadPictureImageSource(const nsAString & aSrcsetAttr,const nsAString & aSizesAttr,const nsAString & aTypeAttr,const nsAString & aMediaAttr)12180 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr,
12181 const nsAString& aSizesAttr,
12182 const nsAString& aTypeAttr,
12183 const nsAString& aMediaAttr) {
12184 // Nested pictures are not valid syntax, so while we'll eventually load them,
12185 // it's not worth tracking sources mixed between nesting levels to preload
12186 // them effectively.
12187 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) {
12188 // <picture> selects the first matching source, so if this returns a URI we
12189 // needn't consider new sources until a new <picture> is encountered.
12190 bool found = HTMLImageElement::SelectSourceForTagWithAttrs(
12191 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr,
12192 aMediaAttr, mPreloadPictureFoundSource);
12193 if (found && mPreloadPictureFoundSource.IsVoid()) {
12194 // Found an empty source, which counts
12195 mPreloadPictureFoundSource.SetIsVoid(false);
12196 }
12197 }
12198 }
12199
ResolvePreloadImage(nsIURI * aBaseURI,const nsAString & aSrcAttr,const nsAString & aSrcsetAttr,const nsAString & aSizesAttr,bool * aIsImgSet)12200 already_AddRefed<nsIURI> Document::ResolvePreloadImage(
12201 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr,
12202 const nsAString& aSizesAttr, bool* aIsImgSet) {
12203 nsString sourceURL;
12204 bool isImgSet;
12205 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) {
12206 // We're in a <picture> element and found a URI from a source previous to
12207 // this image, use it.
12208 sourceURL = mPreloadPictureFoundSource;
12209 isImgSet = true;
12210 } else {
12211 // Otherwise try to use this <img> as a source
12212 HTMLImageElement::SelectSourceForTagWithAttrs(
12213 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(),
12214 VoidString(), sourceURL);
12215 isImgSet = !aSrcsetAttr.IsEmpty();
12216 }
12217
12218 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI)
12219 if (sourceURL.IsEmpty()) {
12220 return nullptr;
12221 }
12222
12223 // Construct into URI using passed baseURI (the parser may know of base URI
12224 // changes that have not reached us)
12225 nsresult rv;
12226 nsCOMPtr<nsIURI> uri;
12227 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL,
12228 this, aBaseURI);
12229 if (NS_FAILED(rv)) {
12230 return nullptr;
12231 }
12232
12233 *aIsImgSet = isImgSet;
12234
12235 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in
12236 // this this <picture> share the same <sources> (though this is not valid per
12237 // spec)
12238 return uri.forget();
12239 }
12240
PreLoadImage(nsIURI * aUri,const nsAString & aCrossOriginAttr,ReferrerPolicyEnum aReferrerPolicy,bool aIsImgSet,bool aLinkPreload)12241 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
12242 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
12243 bool aLinkPreload) {
12244 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
12245 nsIRequest::LOAD_RECORD_START_REQUEST_DELAY |
12246 nsContentUtils::CORSModeToLoadImageFlags(
12247 Element::StringToCORSMode(aCrossOriginAttr));
12248
12249 nsContentPolicyType policyType =
12250 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET
12251 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD;
12252
12253 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12254 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12255
12256 RefPtr<imgRequestProxy> request;
12257 nsresult rv = nsContentUtils::LoadImage(
12258 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
12259 nullptr /* no observer */, loadFlags,
12260 aLinkPreload ? u"link"_ns : u"img"_ns, getter_AddRefs(request),
12261 policyType, false /* urgent */, aLinkPreload);
12262
12263 // Pin image-reference to avoid evicting it from the img-cache before
12264 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
12265 // unlink
12266 if (!aLinkPreload && NS_SUCCEEDED(rv)) {
12267 mPreloadingImages.InsertOrUpdate(aUri, std::move(request));
12268 }
12269 }
12270
MaybePreLoadImage(nsIURI * aUri,const nsAString & aCrossOriginAttr,ReferrerPolicyEnum aReferrerPolicy,bool aIsImgSet,bool aLinkPreload,const TimeStamp & aInitTimestamp)12271 void Document::MaybePreLoadImage(nsIURI* aUri,
12272 const nsAString& aCrossOriginAttr,
12273 ReferrerPolicyEnum aReferrerPolicy,
12274 bool aIsImgSet, bool aLinkPreload,
12275 const TimeStamp& aInitTimestamp) {
12276 if (aLinkPreload) {
12277 // Check if the image was already preloaded in this document to avoid
12278 // duplicate preloading.
12279 PreloadHashKey key = PreloadHashKey::CreateAsImage(
12280 aUri, NodePrincipal(),
12281 dom::Element::StringToCORSMode(aCrossOriginAttr));
12282 if (!mPreloadService.PreloadExists(key)) {
12283 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12284 aLinkPreload);
12285 }
12286 return;
12287 }
12288
12289 // Early exit if the img is already present in the img-cache
12290 // which indicates that the "real" load has already started and
12291 // that we shouldn't preload it.
12292 if (nsContentUtils::IsImageInCache(aUri, this)) {
12293 return;
12294 }
12295
12296 #ifdef NIGHTLY_BUILD
12297 Telemetry::Accumulate(
12298 Telemetry::DOCUMENT_PRELOAD_IMAGE_ASYNCOPEN_DELAY,
12299 static_cast<uint32_t>(
12300 (TimeStamp::Now() - aInitTimestamp).ToMilliseconds()));
12301 #endif
12302
12303 // Image not in cache - trigger preload
12304 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
12305 aLinkPreload);
12306 }
12307
MaybePreconnect(nsIURI * aOrigURI,mozilla::CORSMode aCORSMode)12308 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
12309 NS_MutateURI mutator(aOrigURI);
12310 if (NS_FAILED(mutator.GetStatus())) {
12311 return;
12312 }
12313
12314 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect
12315 // which ignores the path and uses only the origin. The other is for the
12316 // document mPreloadedPreconnects de-duplication hash. Anonymous vs
12317 // non-Anonymous preconnects create different connections on the wire and
12318 // therefore should not be considred duplicates of each other and we
12319 // normalize the path before putting it in the hash to accomplish that.
12320
12321 if (aCORSMode == CORS_ANONYMOUS) {
12322 mutator.SetPathQueryRef("/anonymous"_ns);
12323 } else {
12324 mutator.SetPathQueryRef("/"_ns);
12325 }
12326
12327 nsCOMPtr<nsIURI> uri;
12328 nsresult rv = mutator.Finalize(uri);
12329 if (NS_FAILED(rv)) {
12330 return;
12331 }
12332
12333 const bool existingEntryFound =
12334 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) {
12335 if (entry) {
12336 return true;
12337 }
12338 entry.Insert(true);
12339 return false;
12340 });
12341 if (existingEntryFound) {
12342 return;
12343 }
12344
12345 nsCOMPtr<nsISpeculativeConnect> speculator(
12346 do_QueryInterface(nsContentUtils::GetIOService()));
12347 if (!speculator) {
12348 return;
12349 }
12350
12351 if (aCORSMode == CORS_ANONYMOUS) {
12352 speculator->SpeculativeAnonymousConnect(uri, NodePrincipal(), nullptr);
12353 } else {
12354 speculator->SpeculativeConnect(uri, NodePrincipal(), nullptr);
12355 }
12356 }
12357
ForgetImagePreload(nsIURI * aURI)12358 void Document::ForgetImagePreload(nsIURI* aURI) {
12359 // Checking count is faster than hashing the URI in the common
12360 // case of empty table.
12361 if (mPreloadingImages.Count() != 0) {
12362 nsCOMPtr<imgIRequest> req;
12363 mPreloadingImages.Remove(aURI, getter_AddRefs(req));
12364 if (req) {
12365 // Make sure to cancel the request so imagelib knows it's gone.
12366 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
12367 }
12368 }
12369 }
12370
UpdateDocumentStates(EventStates aMaybeChangedStates,bool aNotify)12371 void Document::UpdateDocumentStates(EventStates aMaybeChangedStates,
12372 bool aNotify) {
12373 const EventStates oldStates = mDocumentState;
12374 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12375 NS_DOCUMENT_STATE_ALL_LOCALEDIR_BITS)) {
12376 mDocumentState &= ~NS_DOCUMENT_STATE_ALL_LOCALEDIR_BITS;
12377 if (IsDocumentRightToLeft()) {
12378 mDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
12379 } else {
12380 mDocumentState |= NS_DOCUMENT_STATE_LTR_LOCALE;
12381 }
12382 }
12383
12384 if (aMaybeChangedStates.HasAtLeastOneOfStates(
12385 NS_DOCUMENT_STATE_ALL_LWTHEME_BITS)) {
12386 mDocumentState &= ~NS_DOCUMENT_STATE_ALL_LWTHEME_BITS;
12387 switch (GetDocumentLWTheme()) {
12388 case DocumentTheme::None:
12389 break;
12390 case DocumentTheme::Bright:
12391 mDocumentState |=
12392 NS_DOCUMENT_STATE_LWTHEME | NS_DOCUMENT_STATE_LWTHEME_BRIGHTTEXT;
12393 break;
12394 case DocumentTheme::Dark:
12395 mDocumentState |=
12396 NS_DOCUMENT_STATE_LWTHEME | NS_DOCUMENT_STATE_LWTHEME_DARKTEXT;
12397 break;
12398 case DocumentTheme::Neutral:
12399 mDocumentState |= NS_DOCUMENT_STATE_LWTHEME;
12400 break;
12401 }
12402 }
12403
12404 if (aMaybeChangedStates.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
12405 if (IsTopLevelWindowInactive()) {
12406 mDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
12407 } else {
12408 mDocumentState &= ~NS_DOCUMENT_STATE_WINDOW_INACTIVE;
12409 }
12410 }
12411
12412 EventStates changedStates = oldStates ^ mDocumentState;
12413 if (aNotify && !changedStates.IsEmpty()) {
12414 if (PresShell* ps = GetObservingPresShell()) {
12415 ps->DocumentStatesChanged(changedStates);
12416 }
12417 }
12418 }
12419
12420 namespace {
12421
12422 /**
12423 * Stub for LoadSheet(), since all we want is to get the sheet into
12424 * the CSSLoader's style cache
12425 */
12426 class StubCSSLoaderObserver final : public nsICSSLoaderObserver {
12427 ~StubCSSLoaderObserver() = default;
12428
12429 public:
12430 NS_IMETHOD
StyleSheetLoaded(StyleSheet *,bool,nsresult)12431 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; }
12432 NS_DECL_ISUPPORTS
12433 };
12434 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
12435
12436 } // namespace
12437
PreloadStyle(nsIURI * uri,const Encoding * aEncoding,const nsAString & aCrossOriginAttr,const enum ReferrerPolicy aReferrerPolicy,const nsAString & aIntegrity,css::StylePreloadKind aKind)12438 SheetPreloadStatus Document::PreloadStyle(
12439 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr,
12440 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aIntegrity,
12441 css::StylePreloadKind aKind) {
12442 MOZ_ASSERT(aKind != css::StylePreloadKind::None);
12443
12444 // The CSSLoader will retain this object after we return.
12445 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
12446
12447 nsCOMPtr<nsIReferrerInfo> referrerInfo =
12448 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
12449
12450 // Charset names are always ASCII.
12451 auto result = CSSLoader()->LoadSheet(
12452 uri, aKind, aEncoding, referrerInfo, obs,
12453 Element::StringToCORSMode(aCrossOriginAttr), aIntegrity);
12454 if (result.isErr()) {
12455 return SheetPreloadStatus::Errored;
12456 }
12457 RefPtr<StyleSheet> sheet = result.unwrap();
12458 if (sheet->IsComplete()) {
12459 return SheetPreloadStatus::AlreadyComplete;
12460 }
12461 return SheetPreloadStatus::InProgress;
12462 }
12463
LoadChromeSheetSync(nsIURI * uri)12464 RefPtr<StyleSheet> Document::LoadChromeSheetSync(nsIURI* uri) {
12465 return CSSLoader()
12466 ->LoadSheetSync(uri, css::eAuthorSheetFeatures)
12467 .unwrapOr(nullptr);
12468 }
12469
ResetDocumentDirection()12470 void Document::ResetDocumentDirection() {
12471 if (!nsContentUtils::IsChromeDoc(this)) {
12472 return;
12473 }
12474 UpdateDocumentStates(NS_DOCUMENT_STATE_ALL_LOCALEDIR_BITS, true);
12475 }
12476
IsDocumentRightToLeft()12477 bool Document::IsDocumentRightToLeft() {
12478 if (!nsContentUtils::IsChromeDoc(this)) {
12479 return false;
12480 }
12481 // setting the localedir attribute on the root element forces a
12482 // specific direction for the document.
12483 Element* element = GetRootElement();
12484 if (element) {
12485 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl,
12486 nullptr};
12487 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
12488 strings, eCaseMatters)) {
12489 case 0:
12490 return false;
12491 case 1:
12492 return true;
12493 default:
12494 break; // otherwise, not a valid value, so fall through
12495 }
12496 }
12497
12498 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") &&
12499 !mDocumentURI->SchemeIs("resource")) {
12500 return false;
12501 }
12502
12503 return intl::LocaleService::GetInstance()->IsAppLocaleRTL();
12504 }
12505
12506 class nsDelayedEventDispatcher : public Runnable {
12507 public:
nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>> && aDocuments)12508 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments)
12509 : mozilla::Runnable("nsDelayedEventDispatcher"),
12510 mDocuments(std::move(aDocuments)) {}
12511 virtual ~nsDelayedEventDispatcher() = default;
12512
Run()12513 NS_IMETHOD Run() override {
12514 FireOrClearDelayedEvents(mDocuments, true);
12515 return NS_OK;
12516 }
12517
12518 private:
12519 nsTArray<nsCOMPtr<Document>> mDocuments;
12520 };
12521
GetAndUnsuppressSubDocuments(Document & aDocument,nsTArray<nsCOMPtr<Document>> & aDocuments)12522 static void GetAndUnsuppressSubDocuments(
12523 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) {
12524 if (aDocument.EventHandlingSuppressed() > 0) {
12525 aDocument.DecreaseEventSuppression();
12526 aDocument.ScriptLoader()->RemoveExecuteBlocker();
12527 }
12528 aDocuments.AppendElement(&aDocument);
12529 auto recurse = [&aDocuments](Document& aSubDoc) {
12530 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments);
12531 return CallState::Continue;
12532 };
12533 aDocument.EnumerateSubDocuments(recurse);
12534 }
12535
UnsuppressEventHandlingAndFireEvents(bool aFireEvents)12536 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) {
12537 nsTArray<nsCOMPtr<Document>> documents;
12538 GetAndUnsuppressSubDocuments(*this, documents);
12539
12540 for (nsCOMPtr<Document>& doc : documents) {
12541 if (!doc->EventHandlingSuppressed()) {
12542 WindowGlobalChild* wgc = doc->GetWindowGlobalChild();
12543 if (wgc) {
12544 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED);
12545 }
12546
12547 MOZ_ASSERT(NS_IsMainThread());
12548 nsTArray<RefPtr<net::ChannelEventQueue>> queues =
12549 std::move(doc->mSuspendedQueues);
12550 for (net::ChannelEventQueue* queue : queues) {
12551 queue->Resume();
12552 }
12553
12554 // If there have been any events driven by the refresh driver which were
12555 // delayed due to events being suppressed in this document, make sure
12556 // there is a refresh scheduled soon so the events will run.
12557 if (doc->mHasDelayedRefreshEvent) {
12558 doc->mHasDelayedRefreshEvent = false;
12559
12560 if (doc->mPresShell) {
12561 nsRefreshDriver* rd =
12562 doc->mPresShell->GetPresContext()->RefreshDriver();
12563 rd->RunDelayedEventsSoon();
12564 }
12565 }
12566 }
12567 }
12568
12569 if (aFireEvents) {
12570 MOZ_RELEASE_ASSERT(NS_IsMainThread());
12571 nsCOMPtr<nsIRunnable> ded =
12572 new nsDelayedEventDispatcher(std::move(documents));
12573 Dispatch(TaskCategory::Other, ded.forget());
12574 } else {
12575 FireOrClearDelayedEvents(documents, false);
12576 }
12577 }
12578
AreClipboardCommandsUnconditionallyEnabled() const12579 bool Document::AreClipboardCommandsUnconditionallyEnabled() const {
12580 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this);
12581 }
12582
AddSuspendedChannelEventQueue(net::ChannelEventQueue * aQueue)12583 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) {
12584 MOZ_ASSERT(NS_IsMainThread());
12585 MOZ_ASSERT(EventHandlingSuppressed());
12586 mSuspendedQueues.AppendElement(aQueue);
12587 }
12588
SuspendPostMessageEvent(PostMessageEvent * aEvent)12589 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) {
12590 MOZ_ASSERT(NS_IsMainThread());
12591
12592 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) {
12593 mSuspendedPostMessageEvents.AppendElement(aEvent);
12594 return true;
12595 }
12596 return false;
12597 }
12598
FireOrClearPostMessageEvents(bool aFireEvents)12599 void Document::FireOrClearPostMessageEvents(bool aFireEvents) {
12600 nsTArray<RefPtr<PostMessageEvent>> events =
12601 std::move(mSuspendedPostMessageEvents);
12602
12603 if (aFireEvents) {
12604 for (PostMessageEvent* event : events) {
12605 event->Run();
12606 }
12607 }
12608 }
12609
SetSuppressedEventListener(EventListener * aListener)12610 void Document::SetSuppressedEventListener(EventListener* aListener) {
12611 mSuppressedEventListener = aListener;
12612 auto setOnSubDocs = [&](Document& aDocument) {
12613 aDocument.SetSuppressedEventListener(aListener);
12614 return CallState::Continue;
12615 };
12616 EnumerateSubDocuments(setOnSubDocs);
12617 }
12618
IsActive() const12619 bool Document::IsActive() const {
12620 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() &&
12621 !GetBrowsingContext()->IsInBFCache();
12622 }
12623
GetCurrentContentSink()12624 nsISupports* Document::GetCurrentContentSink() {
12625 return mParser ? mParser->GetContentSink() : nullptr;
12626 }
12627
GetTemplateContentsOwner()12628 Document* Document::GetTemplateContentsOwner() {
12629 if (!mTemplateContentsOwner) {
12630 bool hasHadScriptObject = true;
12631 nsIScriptGlobalObject* scriptObject =
12632 GetScriptHandlingObject(hasHadScriptObject);
12633
12634 nsCOMPtr<Document> document;
12635 nsresult rv = NS_NewDOMDocument(
12636 getter_AddRefs(document),
12637 u""_ns, // aNamespaceURI
12638 u""_ns, // aQualifiedName
12639 nullptr, // aDoctype
12640 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(),
12641 true, // aLoadedAsData
12642 scriptObject, // aEventObject
12643 IsHTMLDocument() ? DocumentFlavorHTML : DocumentFlavorXML);
12644 NS_ENSURE_SUCCESS(rv, nullptr);
12645
12646 mTemplateContentsOwner = document;
12647 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
12648
12649 if (!scriptObject) {
12650 mTemplateContentsOwner->SetScopeObject(GetScopeObject());
12651 }
12652
12653 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject;
12654
12655 // Set |mTemplateContentsOwner| as the template contents owner of itself so
12656 // that it is the template contents owner of nested template elements.
12657 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner;
12658 }
12659
12660 return mTemplateContentsOwner;
12661 }
12662
FindTopWindowForElement(Element * element)12663 static already_AddRefed<nsPIDOMWindowOuter> FindTopWindowForElement(
12664 Element* element) {
12665 Document* document = element->OwnerDoc();
12666 if (!document) {
12667 return nullptr;
12668 }
12669
12670 nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow();
12671 if (!window) {
12672 return nullptr;
12673 }
12674
12675 // Trying to find the top window (equivalent to window.top).
12676 if (nsCOMPtr<nsPIDOMWindowOuter> top = window->GetInProcessTop()) {
12677 window = std::move(top);
12678 }
12679 return window.forget();
12680 }
12681
12682 /**
12683 * nsAutoFocusEvent is used to dispatch a focus event for an
12684 * nsGenericHTMLFormElement with the autofocus attribute enabled.
12685 */
12686 class nsAutoFocusEvent : public Runnable {
12687 public:
nsAutoFocusEvent(nsCOMPtr<Element> && aElement,nsCOMPtr<nsPIDOMWindowOuter> && aTopWindow)12688 explicit nsAutoFocusEvent(nsCOMPtr<Element>&& aElement,
12689 nsCOMPtr<nsPIDOMWindowOuter>&& aTopWindow)
12690 : mozilla::Runnable("nsAutoFocusEvent"),
12691 mElement(std::move(aElement)),
12692 mTopWindow(std::move(aTopWindow)) {}
12693
Run()12694 NS_IMETHOD Run() override {
12695 nsCOMPtr<nsPIDOMWindowOuter> currentTopWindow =
12696 FindTopWindowForElement(mElement);
12697 if (currentTopWindow != mTopWindow) {
12698 // The element's top window changed from when the event was queued.
12699 // Don't take away focus from an unrelated window.
12700 return NS_OK;
12701 }
12702
12703 if (Document* doc = mTopWindow->GetExtantDoc()) {
12704 if (doc->IsAutoFocusFired()) {
12705 return NS_OK;
12706 }
12707 doc->SetAutoFocusFired();
12708 }
12709
12710 // Don't steal focus from the user.
12711 if (mTopWindow->GetFocusedElement()) {
12712 return NS_OK;
12713 }
12714
12715 FocusOptions options;
12716 ErrorResult rv;
12717 mElement->Focus(options, CallerType::System, rv);
12718 return rv.StealNSResult();
12719 }
12720
12721 private:
12722 nsCOMPtr<Element> mElement;
12723 nsCOMPtr<nsPIDOMWindowOuter> mTopWindow;
12724 };
12725
SetAutoFocusElement(Element * aAutoFocusElement)12726 void Document::SetAutoFocusElement(Element* aAutoFocusElement) {
12727 if (mAutoFocusFired) {
12728 // Too late.
12729 return;
12730 }
12731
12732 if (mAutoFocusElement) {
12733 // The spec disallows multiple autofocus elements, so we consider only the
12734 // first one to preserve the old behavior.
12735 return;
12736 }
12737
12738 mAutoFocusElement = do_GetWeakReference(aAutoFocusElement);
12739 TriggerAutoFocus();
12740 }
12741
SetAutoFocusFired()12742 void Document::SetAutoFocusFired() { mAutoFocusFired = true; }
12743
IsAutoFocusFired()12744 bool Document::IsAutoFocusFired() { return mAutoFocusFired; }
12745
TriggerAutoFocus()12746 void Document::TriggerAutoFocus() {
12747 if (mAutoFocusFired) {
12748 return;
12749 }
12750
12751 if (!mPresShell || !mPresShell->DidInitialize()) {
12752 // Delay autofocus until frames are constructed so that we don't thrash
12753 // style and layout calculations.
12754 return;
12755 }
12756
12757 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(mAutoFocusElement);
12758 if (autoFocusElement && autoFocusElement->OwnerDoc() == this) {
12759 nsCOMPtr<nsPIDOMWindowOuter> topWindow =
12760 FindTopWindowForElement(autoFocusElement);
12761 if (!topWindow) {
12762 return;
12763 }
12764
12765 // NOTE: This may be removed in the future since the spec technically
12766 // allows autofocus after load.
12767 nsCOMPtr<Document> topDoc = topWindow->GetExtantDoc();
12768 if (topDoc &&
12769 topDoc->GetReadyStateEnum() == Document::READYSTATE_COMPLETE) {
12770 return;
12771 }
12772
12773 nsCOMPtr<nsIRunnable> event =
12774 new nsAutoFocusEvent(std::move(autoFocusElement), topWindow.forget());
12775 nsresult rv = NS_DispatchToCurrentThread(event.forget());
12776 NS_ENSURE_SUCCESS_VOID(rv);
12777 }
12778 }
12779
SetScrollToRef(nsIURI * aDocumentURI)12780 void Document::SetScrollToRef(nsIURI* aDocumentURI) {
12781 if (!aDocumentURI) {
12782 return;
12783 }
12784
12785 nsAutoCString ref;
12786
12787 // Since all URI's that pass through here aren't URL's we can't
12788 // rely on the nsIURI implementation for providing a way for
12789 // finding the 'ref' part of the URI, we'll haveto revert to
12790 // string routines for finding the data past '#'
12791
12792 nsresult rv = aDocumentURI->GetSpec(ref);
12793 if (NS_FAILED(rv)) {
12794 Unused << aDocumentURI->GetRef(mScrollToRef);
12795 return;
12796 }
12797
12798 nsReadingIterator<char> start, end;
12799
12800 ref.BeginReading(start);
12801 ref.EndReading(end);
12802
12803 if (FindCharInReadable('#', start, end)) {
12804 ++start; // Skip over the '#'
12805
12806 mScrollToRef = Substring(start, end);
12807 }
12808 }
12809
ScrollToRef()12810 void Document::ScrollToRef() {
12811 if (mScrolledToRefAlready) {
12812 RefPtr<PresShell> presShell = GetPresShell();
12813 if (presShell) {
12814 presShell->ScrollToAnchor();
12815 }
12816 return;
12817 }
12818
12819 if (mScrollToRef.IsEmpty()) {
12820 return;
12821 }
12822
12823 RefPtr<PresShell> presShell = GetPresShell();
12824 if (presShell) {
12825 nsresult rv = NS_ERROR_FAILURE;
12826 // We assume that the bytes are in UTF-8, as it says in the spec:
12827 // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
12828 NS_ConvertUTF8toUTF16 ref(mScrollToRef);
12829 // Check an empty string which might be caused by the UTF-8 conversion
12830 if (!ref.IsEmpty()) {
12831 // Note that GoToAnchor will handle flushing layout as needed.
12832 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
12833 } else {
12834 rv = NS_ERROR_FAILURE;
12835 }
12836
12837 if (NS_FAILED(rv)) {
12838 nsAutoCString buff;
12839 const bool unescaped =
12840 NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
12841 /*aFlags =*/0, buff);
12842
12843 // This attempt is only necessary if characters were unescaped.
12844 if (unescaped) {
12845 NS_ConvertUTF8toUTF16 utf16Str(buff);
12846 if (!utf16Str.IsEmpty()) {
12847 rv = presShell->GoToAnchor(utf16Str,
12848 mChangeScrollPosWhenScrollingToRef);
12849 }
12850 }
12851
12852 // If UTF-8 URI failed then try to assume the string as a
12853 // document's charset.
12854 if (NS_FAILED(rv)) {
12855 const Encoding* encoding = GetDocumentCharacterSet();
12856 rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
12857 ref);
12858 if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
12859 rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
12860 }
12861 }
12862 }
12863 if (NS_SUCCEEDED(rv)) {
12864 mScrolledToRefAlready = true;
12865 }
12866 }
12867 }
12868
RegisterActivityObserver(nsISupports * aSupports)12869 void Document::RegisterActivityObserver(nsISupports* aSupports) {
12870 if (!mActivityObservers) {
12871 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>();
12872 }
12873 mActivityObservers->Insert(aSupports);
12874 }
12875
UnregisterActivityObserver(nsISupports * aSupports)12876 bool Document::UnregisterActivityObserver(nsISupports* aSupports) {
12877 if (!mActivityObservers) {
12878 return false;
12879 }
12880 return mActivityObservers->EnsureRemoved(aSupports);
12881 }
12882
EnumerateActivityObservers(ActivityObserverEnumerator aEnumerator)12883 void Document::EnumerateActivityObservers(
12884 ActivityObserverEnumerator aEnumerator) {
12885 if (!mActivityObservers) {
12886 return;
12887 }
12888
12889 const auto keyArray =
12890 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers);
12891 for (auto& observer : keyArray) {
12892 aEnumerator(observer.get());
12893 }
12894 }
12895
RegisterPendingLinkUpdate(Link * aLink)12896 void Document::RegisterPendingLinkUpdate(Link* aLink) {
12897 if (aLink->HasPendingLinkUpdate()) {
12898 return;
12899 }
12900
12901 aLink->SetHasPendingLinkUpdate();
12902
12903 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) {
12904 nsCOMPtr<nsIRunnable> event =
12905 NewRunnableMethod("Document::FlushPendingLinkUpdates", this,
12906 &Document::FlushPendingLinkUpdates);
12907 // Do this work in a second in the worst case.
12908 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000,
12909 EventQueuePriority::Idle);
12910 if (NS_FAILED(rv)) {
12911 // If during shutdown posting a runnable doesn't succeed, we probably
12912 // don't need to update link states.
12913 return;
12914 }
12915 mHasLinksToUpdateRunnable = true;
12916 }
12917
12918 mLinksToUpdate.InfallibleAppend(aLink);
12919 }
12920
FlushPendingLinkUpdates()12921 void Document::FlushPendingLinkUpdates() {
12922 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates);
12923 MOZ_ASSERT(mHasLinksToUpdateRunnable);
12924 mHasLinksToUpdateRunnable = false;
12925
12926 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; });
12927 mFlushingPendingLinkUpdates = true;
12928
12929 while (!mLinksToUpdate.IsEmpty()) {
12930 LinksToUpdateList links(std::move(mLinksToUpdate));
12931 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) {
12932 Link* link = iter.Get();
12933 Element* element = link->GetElement();
12934 if (element->OwnerDoc() == this) {
12935 link->ClearHasPendingLinkUpdate();
12936 if (element->IsInComposedDoc()) {
12937 element->UpdateLinkState(link->LinkState());
12938 }
12939 }
12940 }
12941 }
12942 }
12943
12944 /**
12945 * Retrieves the node in a static-clone document that corresponds to aOrigNode,
12946 * which is a node in the original document from which aStaticClone was cloned.
12947 */
GetCorrespondingNodeInDocument(const nsINode * aOrigNode,Document & aStaticClone)12948 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode,
12949 Document& aStaticClone) {
12950 MOZ_ASSERT(aOrigNode);
12951
12952 // Selections in anonymous subtrees aren't supported.
12953 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) {
12954 return nullptr;
12955 }
12956
12957 // If the node is disconnected, this is a bug in the selection code, but it
12958 // can happen with shadow DOM so handle it.
12959 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) {
12960 return nullptr;
12961 }
12962
12963 AutoTArray<Maybe<uint32_t>, 32> indexArray;
12964 const nsINode* current = aOrigNode;
12965 while (const nsINode* parent = current->GetParentNode()) {
12966 Maybe<uint32_t> index = parent->ComputeIndexOf(current);
12967 NS_ENSURE_TRUE(index.isSome(), nullptr);
12968 indexArray.AppendElement(std::move(index));
12969 current = parent;
12970 }
12971 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot());
12972 nsINode* correspondingNode = [&]() -> nsINode* {
12973 if (current->IsDocument()) {
12974 return &aStaticClone;
12975 }
12976 const auto* shadow = ShadowRoot::FromNode(*current);
12977 if (!shadow) {
12978 return nullptr;
12979 }
12980 nsINode* correspondingHost =
12981 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone);
12982 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) {
12983 return nullptr;
12984 }
12985 return correspondingHost->AsElement()->GetShadowRoot();
12986 }();
12987
12988 if (NS_WARN_IF(!correspondingNode)) {
12989 return nullptr;
12990 }
12991 for (const Maybe<uint32_t>& index : Reversed(indexArray)) {
12992 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index);
12993 NS_ENSURE_TRUE(correspondingNode, nullptr);
12994 }
12995 return correspondingNode;
12996 }
12997
12998 /**
12999 * Caches the selection ranges from the source document onto the static clone in
13000 * case the "Print Selection Only" functionality is invoked.
13001 *
13002 * Note that we cannot use the selection obtained from GetOriginalDocument()
13003 * since that selection may have mutated after the print was invoked.
13004 *
13005 * Note also that because nsRange objects point into a specific document's
13006 * nodes, we cannot reuse an array of nsRange objects across multiple static
13007 * clone documents. For that reason we cache a new array of ranges on each
13008 * static clone that we create.
13009 *
13010 * TODO(emilio): This can be simplified once we don't re-clone from static
13011 * documents.
13012 *
13013 * @param aSourceDoc the document from which we are caching selection ranges
13014 * @param aStaticClone the document that will hold the cache
13015 * @return true if a selection range was cached
13016 */
CachePrintSelectionRanges(const Document & aSourceDoc,Document & aStaticClone)13017 static void CachePrintSelectionRanges(const Document& aSourceDoc,
13018 Document& aStaticClone) {
13019 MOZ_ASSERT(aStaticClone.IsStaticDocument());
13020 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges));
13021
13022 const Selection* origSelection = nullptr;
13023 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr;
13024 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument();
13025
13026 if (sourceDocIsStatic) {
13027 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
13028 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges));
13029 } else if (PresShell* shell = aSourceDoc.GetPresShell()) {
13030 origSelection = shell->GetCurrentSelection(SelectionType::eNormal);
13031 }
13032
13033 if (!origSelection && !origRanges) {
13034 return;
13035 }
13036
13037 const uint32_t rangeCount =
13038 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount();
13039 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount);
13040
13041 for (const uint32_t i : IntegerRange(rangeCount)) {
13042 MOZ_ASSERT_IF(!sourceDocIsStatic,
13043 origSelection->RangeCount() == rangeCount);
13044 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get()
13045 : origSelection->GetRangeAt(i);
13046 MOZ_ASSERT(range);
13047 nsINode* startContainer = range->GetStartContainer();
13048 nsINode* endContainer = range->GetEndContainer();
13049
13050 if (!startContainer || !endContainer) {
13051 continue;
13052 }
13053
13054 nsINode* startNode =
13055 GetCorrespondingNodeInDocument(startContainer, aStaticClone);
13056 nsINode* endNode =
13057 GetCorrespondingNodeInDocument(endContainer, aStaticClone);
13058
13059 if (NS_WARN_IF(!startNode || !endNode)) {
13060 continue;
13061 }
13062
13063 RefPtr<nsRange> clonedRange =
13064 nsRange::Create(startNode, range->StartOffset(), endNode,
13065 range->EndOffset(), IgnoreErrors());
13066 if (clonedRange && !clonedRange->Collapsed()) {
13067 printRanges->AppendElement(std::move(clonedRange));
13068 }
13069 }
13070
13071 if (printRanges->IsEmpty()) {
13072 return;
13073 }
13074
13075 aStaticClone.SetProperty(nsGkAtoms::printselectionranges,
13076 printRanges.release(),
13077 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>);
13078 }
13079
CreateStaticClone(nsIDocShell * aCloneContainer,nsIContentViewer * aViewer,nsIPrintSettings * aPrintSettings,bool * aOutHasInProcessPrintCallbacks)13080 already_AddRefed<Document> Document::CreateStaticClone(
13081 nsIDocShell* aCloneContainer, nsIContentViewer* aViewer,
13082 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) {
13083 MOZ_ASSERT(!mCreatingStaticClone);
13084 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones));
13085 MOZ_DIAGNOSTIC_ASSERT(aViewer);
13086
13087 mCreatingStaticClone = true;
13088 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(),
13089 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>);
13090
13091 auto raii = MakeScopeExit([&] {
13092 RemoveProperty(nsGkAtoms::adoptedsheetclones);
13093 mCreatingStaticClone = false;
13094 });
13095
13096 // Make document use different container during cloning.
13097 //
13098 // FIXME(emilio): Why is this needed?
13099 RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
13100 SetContainer(nsDocShell::Cast(aCloneContainer));
13101 IgnoredErrorResult rv;
13102 nsCOMPtr<nsINode> clonedNode = this->CloneNode(true, rv);
13103 SetContainer(originalShell);
13104 if (rv.Failed()) {
13105 return nullptr;
13106 }
13107
13108 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode);
13109 if (!clonedDoc) {
13110 return nullptr;
13111 }
13112
13113 size_t sheetsCount = SheetCount();
13114 for (size_t i = 0; i < sheetsCount; ++i) {
13115 RefPtr<StyleSheet> sheet = SheetAt(i);
13116 if (sheet) {
13117 if (sheet->IsApplicable()) {
13118 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13119 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13120 if (clonedSheet) {
13121 clonedDoc->AddStyleSheet(clonedSheet);
13122 }
13123 }
13124 }
13125 }
13126 clonedDoc->CloneAdoptedSheetsFrom(*this);
13127
13128 for (int t = 0; t < AdditionalSheetTypeCount; ++t) {
13129 auto& sheets = mAdditionalSheets[additionalSheetType(t)];
13130 for (StyleSheet* sheet : sheets) {
13131 if (sheet->IsApplicable()) {
13132 RefPtr<StyleSheet> clonedSheet = sheet->Clone(nullptr, clonedDoc);
13133 NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!");
13134 if (clonedSheet) {
13135 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t),
13136 clonedSheet);
13137 }
13138 }
13139 }
13140 }
13141
13142 // Font faces created with the JS API will not be reflected in the
13143 // stylesheets and need to be copied over to the cloned document.
13144 if (const FontFaceSet* set = GetFonts()) {
13145 set->CopyNonRuleFacesTo(clonedDoc->Fonts());
13146 }
13147
13148 clonedDoc->mReferrerInfo =
13149 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone();
13150 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo;
13151 CachePrintSelectionRanges(*this, *clonedDoc);
13152
13153 // We're done with the clone, embed ourselves into the document viewer and
13154 // clone our children. The order here is pretty important, because our
13155 // document our document needs to have an owner global before we can create
13156 // the frame loaders for subdocuments.
13157 aViewer->SetDocument(clonedDoc);
13158
13159 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks();
13160
13161 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones);
13162 for (const auto& clone : pendingClones) {
13163 RefPtr<Element> element = do_QueryObject(clone.mElement);
13164 RefPtr<nsFrameLoader> frameLoader =
13165 nsFrameLoader::Create(element, /* aNetworkCreated */ false);
13166
13167 if (NS_WARN_IF(!frameLoader)) {
13168 continue;
13169 }
13170
13171 clone.mElement->SetFrameLoader(frameLoader);
13172
13173 nsresult rv = frameLoader->FinishStaticClone(
13174 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks);
13175 Unused << NS_WARN_IF(NS_FAILED(rv));
13176 }
13177
13178 return clonedDoc.forget();
13179 }
13180
UnlinkOriginalDocumentIfStatic()13181 void Document::UnlinkOriginalDocumentIfStatic() {
13182 if (IsStaticDocument() && mOriginalDocument) {
13183 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
13184 mOriginalDocument->mStaticCloneCount--;
13185 mOriginalDocument = nullptr;
13186 }
13187 MOZ_ASSERT(!mOriginalDocument);
13188 }
13189
ScheduleFrameRequestCallback(FrameRequestCallback & aCallback,int32_t * aHandle)13190 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
13191 int32_t* aHandle) {
13192 nsresult rv = mFrameRequestManager.Schedule(aCallback, aHandle);
13193 if (NS_FAILED(rv)) {
13194 return rv;
13195 }
13196
13197 UpdateFrameRequestCallbackSchedulingState();
13198 return NS_OK;
13199 }
13200
CancelFrameRequestCallback(int32_t aHandle)13201 void Document::CancelFrameRequestCallback(int32_t aHandle) {
13202 if (mFrameRequestManager.Cancel(aHandle)) {
13203 UpdateFrameRequestCallbackSchedulingState();
13204 }
13205 }
13206
IsCanceledFrameRequestCallback(int32_t aHandle) const13207 bool Document::IsCanceledFrameRequestCallback(int32_t aHandle) const {
13208 return mFrameRequestManager.IsCanceled(aHandle);
13209 }
13210
GetStateObject(nsIVariant ** aState)13211 nsresult Document::GetStateObject(nsIVariant** aState) {
13212 // Get the document's current state object. This is the object backing both
13213 // history.state and popStateEvent.state.
13214 //
13215 // mStateObjectContainer may be null; this just means that there's no
13216 // current state object.
13217
13218 if (!mStateObjectCached && mStateObjectContainer) {
13219 AutoJSAPI jsapi;
13220 // Init with null is "OK" in the sense that it will just fail.
13221 if (!jsapi.Init(GetScopeObject())) {
13222 return NS_ERROR_UNEXPECTED;
13223 }
13224 mStateObjectContainer->DeserializeToVariant(
13225 jsapi.cx(), getter_AddRefs(mStateObjectCached));
13226 }
13227
13228 NS_IF_ADDREF(*aState = mStateObjectCached);
13229 return NS_OK;
13230 }
13231
SetNavigationTiming(nsDOMNavigationTiming * aTiming)13232 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
13233 mTiming = aTiming;
13234 if (!mLoadingTimeStamp.IsNull() && mTiming) {
13235 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), mLoadingTimeStamp);
13236 }
13237 }
13238
ImageMapList()13239 nsContentList* Document::ImageMapList() {
13240 if (!mImageMaps) {
13241 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map,
13242 nsGkAtoms::map);
13243 }
13244
13245 return mImageMaps;
13246 }
13247
13248 #define DEPRECATED_OPERATION(_op) #_op "Warning",
13249 static const char* kDeprecationWarnings[] = {
13250 #include "nsDeprecatedOperationList.h"
13251 nullptr};
13252 #undef DEPRECATED_OPERATION
13253
13254 #define DOCUMENT_WARNING(_op) #_op "Warning",
13255 static const char* kDocumentWarnings[] = {
13256 #include "nsDocumentWarningList.h"
13257 nullptr};
13258 #undef DOCUMENT_WARNING
13259
OperationToUseCounter(DeprecatedOperations aOperation)13260 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) {
13261 switch (aOperation) {
13262 #define DEPRECATED_OPERATION(_op) \
13263 case DeprecatedOperations::e##_op: \
13264 return eUseCounter_##_op;
13265 #include "nsDeprecatedOperationList.h"
13266 #undef DEPRECATED_OPERATION
13267 default:
13268 MOZ_CRASH();
13269 }
13270 }
13271
HasWarnedAbout(DeprecatedOperations aOperation) const13272 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const {
13273 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)];
13274 }
13275
WarnOnceAbout(DeprecatedOperations aOperation,bool asError,const nsTArray<nsString> & aParams) const13276 void Document::WarnOnceAbout(
13277 DeprecatedOperations aOperation, bool asError /* = false */,
13278 const nsTArray<nsString>& aParams /* = empty array */) const {
13279 MOZ_ASSERT(NS_IsMainThread());
13280 if (HasWarnedAbout(aOperation)) {
13281 return;
13282 }
13283 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true;
13284 // Don't count deprecated operations for about pages since those pages
13285 // are almost in our control, and we always need to remove uses there
13286 // before we remove the operation itself anyway.
13287 if (!IsAboutPage()) {
13288 const_cast<Document*>(this)->SetUseCounter(
13289 OperationToUseCounter(aOperation));
13290 }
13291 uint32_t flags =
13292 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13293 nsContentUtils::ReportToConsole(
13294 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES,
13295 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams);
13296 }
13297
HasWarnedAbout(DocumentWarnings aWarning) const13298 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const {
13299 return mDocWarningWarnedAbout[aWarning];
13300 }
13301
WarnOnceAbout(DocumentWarnings aWarning,bool asError,const nsTArray<nsString> & aParams) const13302 void Document::WarnOnceAbout(
13303 DocumentWarnings aWarning, bool asError /* = false */,
13304 const nsTArray<nsString>& aParams /* = empty array */) const {
13305 MOZ_ASSERT(NS_IsMainThread());
13306 if (HasWarnedAbout(aWarning)) {
13307 return;
13308 }
13309 mDocWarningWarnedAbout[aWarning] = true;
13310 uint32_t flags =
13311 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag;
13312 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this,
13313 nsContentUtils::eDOM_PROPERTIES,
13314 kDocumentWarnings[aWarning], aParams);
13315 }
13316
ImageTracker()13317 mozilla::dom::ImageTracker* Document::ImageTracker() {
13318 if (!mImageTracker) {
13319 mImageTracker = new mozilla::dom::ImageTracker;
13320 }
13321 return mImageTracker;
13322 }
13323
ScheduleSVGUseElementShadowTreeUpdate(SVGUseElement & aUseElement)13324 void Document::ScheduleSVGUseElementShadowTreeUpdate(
13325 SVGUseElement& aUseElement) {
13326 MOZ_ASSERT(aUseElement.IsInComposedDoc());
13327
13328 if (MOZ_UNLIKELY(mIsStaticDocument)) {
13329 // Printing doesn't deal well with dynamic DOM mutations.
13330 return;
13331 }
13332
13333 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement);
13334
13335 if (PresShell* presShell = GetPresShell()) {
13336 presShell->EnsureStyleFlush();
13337 }
13338 }
13339
DoUpdateSVGUseElementShadowTrees()13340 void Document::DoUpdateSVGUseElementShadowTrees() {
13341 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13342
13343 do {
13344 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>(
13345 mSVGUseElementsNeedingShadowTreeUpdate);
13346 mSVGUseElementsNeedingShadowTreeUpdate.Clear();
13347
13348 for (const auto& useElement : useElementsToUpdate) {
13349 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) {
13350 // The element was in another <use> shadow tree which we processed
13351 // already and also needed an update, and is removed from the document
13352 // now, so nothing to do here.
13353 MOZ_ASSERT(useElementsToUpdate.Length() > 1);
13354 continue;
13355 }
13356 useElement->UpdateShadowTree();
13357 }
13358 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty());
13359 }
13360
NotifyMediaFeatureValuesChanged()13361 void Document::NotifyMediaFeatureValuesChanged() {
13362 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) {
13363 imageElement->MediaFeatureValuesChanged();
13364 }
13365 }
13366
CreateTouch(nsGlobalWindowInner * aView,EventTarget * aTarget,int32_t aIdentifier,int32_t aPageX,int32_t aPageY,int32_t aScreenX,int32_t aScreenY,int32_t aClientX,int32_t aClientY,int32_t aRadiusX,int32_t aRadiusY,float aRotationAngle,float aForce)13367 already_AddRefed<Touch> Document::CreateTouch(
13368 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier,
13369 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY,
13370 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY,
13371 float aRotationAngle, float aForce) {
13372 RefPtr<Touch> touch =
13373 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY,
13374 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce);
13375 return touch.forget();
13376 }
13377
CreateTouchList()13378 already_AddRefed<TouchList> Document::CreateTouchList() {
13379 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13380 return retval.forget();
13381 }
13382
CreateTouchList(Touch & aTouch,const Sequence<OwningNonNull<Touch>> & aTouches)13383 already_AddRefed<TouchList> Document::CreateTouchList(
13384 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) {
13385 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13386 retval->Append(&aTouch);
13387 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13388 retval->Append(aTouches[i].get());
13389 }
13390 return retval.forget();
13391 }
13392
CreateTouchList(const Sequence<OwningNonNull<Touch>> & aTouches)13393 already_AddRefed<TouchList> Document::CreateTouchList(
13394 const Sequence<OwningNonNull<Touch>>& aTouches) {
13395 RefPtr<TouchList> retval = new TouchList(ToSupports(this));
13396 for (uint32_t i = 0; i < aTouches.Length(); ++i) {
13397 retval->Append(aTouches[i].get());
13398 }
13399 return retval.forget();
13400 }
13401
CaretPositionFromPoint(float aX,float aY)13402 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
13403 float aX, float aY) {
13404 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
13405
13406 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
13407 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
13408 nsPoint pt(x, y);
13409
13410 FlushPendingNotifications(FlushType::Layout);
13411
13412 PresShell* presShell = GetPresShell();
13413 if (!presShell) {
13414 return nullptr;
13415 }
13416
13417 nsIFrame* rootFrame = presShell->GetRootFrame();
13418
13419 // XUL docs, unlike HTML, have no frame tree until everything's done loading
13420 if (!rootFrame) {
13421 return nullptr;
13422 }
13423
13424 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
13425 RelativeTo{rootFrame}, pt,
13426 {{FrameForPointOption::IgnorePaintSuppression,
13427 FrameForPointOption::IgnoreCrossDoc}});
13428 if (!ptFrame) {
13429 return nullptr;
13430 }
13431
13432 // We require frame-relative coordinates for GetContentOffsetsFromPoint.
13433 nsPoint aOffset;
13434 nsCOMPtr<nsIWidget> widget = nsContentUtils::GetWidget(presShell, &aOffset);
13435 LayoutDeviceIntPoint refPoint = nsContentUtils::ToWidgetPoint(
13436 CSSPoint(aX, aY), aOffset, GetPresContext());
13437 nsPoint adjustedPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
13438 widget, refPoint, RelativeTo{ptFrame});
13439
13440 nsIFrame::ContentOffsets offsets =
13441 ptFrame->GetContentOffsetsFromPoint(adjustedPoint);
13442
13443 nsCOMPtr<nsIContent> node = offsets.content;
13444 uint32_t offset = offsets.offset;
13445 nsCOMPtr<nsIContent> anonNode = node;
13446 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
13447 if (nodeIsAnonymous) {
13448 node = ptFrame->GetContent();
13449 nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
13450 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
13451 nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
13452 if (textFrame) {
13453 // If the anonymous content node has a child, then we need to make sure
13454 // that we get the appropriate child, as otherwise the offset may not be
13455 // correct when we construct a range for it.
13456 nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
13457 if (firstChild) {
13458 anonNode = firstChild;
13459 }
13460
13461 if (textArea) {
13462 offset =
13463 nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset);
13464 }
13465
13466 node = nonanon;
13467 } else {
13468 node = nullptr;
13469 offset = 0;
13470 }
13471 }
13472
13473 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset);
13474 if (nodeIsAnonymous) {
13475 aCaretPos->SetAnonymousContentNode(anonNode);
13476 }
13477 return aCaretPos.forget();
13478 }
13479
IsPotentiallyScrollable(HTMLBodyElement * aBody)13480 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) {
13481 // We rely on correct frame information here, so need to flush frames.
13482 FlushPendingNotifications(FlushType::Frames);
13483
13484 // An element that is the HTML body element is potentially scrollable if all
13485 // of the following conditions are true:
13486
13487 // The element has an associated CSS layout box.
13488 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody);
13489 if (!bodyFrame) {
13490 return false;
13491 }
13492
13493 // The element's parent element's computed value of the overflow-x and
13494 // overflow-y properties are visible.
13495 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
13496 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent());
13497 if (parentFrame &&
13498 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) {
13499 return false;
13500 }
13501
13502 // The element's computed value of the overflow-x or overflow-y properties is
13503 // not visible.
13504 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis();
13505 }
13506
GetScrollingElement()13507 Element* Document::GetScrollingElement() {
13508 // Keep this in sync with IsScrollingElement.
13509 if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
13510 RefPtr<HTMLBodyElement> body = GetBodyElement();
13511 if (body && !IsPotentiallyScrollable(body)) {
13512 return body;
13513 }
13514
13515 return nullptr;
13516 }
13517
13518 return GetRootElement();
13519 }
13520
IsScrollingElement(Element * aElement)13521 bool Document::IsScrollingElement(Element* aElement) {
13522 // Keep this in sync with GetScrollingElement.
13523 MOZ_ASSERT(aElement);
13524
13525 if (GetCompatibilityMode() != eCompatibility_NavQuirks) {
13526 return aElement == GetRootElement();
13527 }
13528
13529 // In the common case when aElement != body, avoid refcounting.
13530 HTMLBodyElement* body = GetBodyElement();
13531 if (aElement != body) {
13532 return false;
13533 }
13534
13535 // Now we know body is non-null, since aElement is not null. It's the
13536 // scrolling element for the document if it itself is not potentially
13537 // scrollable.
13538 RefPtr<HTMLBodyElement> strongBody(body);
13539 return !IsPotentiallyScrollable(strongBody);
13540 }
13541
13542 class UnblockParsingPromiseHandler final : public PromiseNativeHandler {
13543 public:
13544 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)13545 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler)
13546
13547 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise,
13548 const BlockParsingOptions& aOptions)
13549 : mPromise(aPromise) {
13550 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull();
13551 if (parser &&
13552 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) {
13553 parser->BlockParser();
13554 mParser = do_GetWeakReference(parser);
13555 mDocument = aDocument;
13556 mDocument->BlockOnload();
13557 mDocument->BlockDOMContentLoaded();
13558 }
13559 }
13560
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)13561 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13562 ErrorResult& aRv) override {
13563 MaybeUnblockParser();
13564
13565 mPromise->MaybeResolve(aValue);
13566 }
13567
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue,ErrorResult & aRv)13568 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
13569 ErrorResult& aRv) override {
13570 MaybeUnblockParser();
13571
13572 mPromise->MaybeReject(aValue);
13573 }
13574
13575 protected:
~UnblockParsingPromiseHandler()13576 virtual ~UnblockParsingPromiseHandler() {
13577 // If we're being cleaned up by the cycle collector, our mDocument reference
13578 // may have been unlinked while our mParser weak reference is still alive.
13579 if (mDocument) {
13580 MaybeUnblockParser();
13581 }
13582 }
13583
13584 private:
MaybeUnblockParser()13585 void MaybeUnblockParser() {
13586 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
13587 if (parser) {
13588 MOZ_DIAGNOSTIC_ASSERT(mDocument);
13589 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
13590 if (parser == docParser) {
13591 parser->UnblockParser();
13592 parser->ContinueInterruptedParsingAsync();
13593 }
13594 }
13595 if (mDocument) {
13596 // We blocked DOMContentLoaded and load events on this document. Unblock
13597 // them. Note that we want to do that no matter what's going on with the
13598 // parser state for this document. Maybe someone caused it to stop being
13599 // parsed, so CreatorParserOrNull() is returning null, but we still want
13600 // to unblock these.
13601 mDocument->UnblockDOMContentLoaded();
13602 mDocument->UnblockOnload(false);
13603 }
13604 mParser = nullptr;
13605 mDocument = nullptr;
13606 }
13607
13608 nsWeakPtr mParser;
13609 RefPtr<Promise> mPromise;
13610 RefPtr<Document> mDocument;
13611 };
13612
NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler,mDocument,mPromise)13613 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise)
13614
13615 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler)
13616 NS_INTERFACE_MAP_ENTRY(nsISupports)
13617 NS_INTERFACE_MAP_END
13618
13619 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler)
13620 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
13621
13622 already_AddRefed<Promise> Document::BlockParsing(
13623 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) {
13624 RefPtr<Promise> resultPromise =
13625 Promise::Create(aPromise.GetParentObject(), aRv);
13626 if (aRv.Failed()) {
13627 return nullptr;
13628 }
13629
13630 RefPtr<PromiseNativeHandler> promiseHandler =
13631 new UnblockParsingPromiseHandler(this, resultPromise, aOptions);
13632 aPromise.AppendNativeHandler(promiseHandler);
13633
13634 return resultPromise.forget();
13635 }
13636
GetMozDocumentURIIfNotForErrorPages()13637 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() {
13638 if (mFailedChannel) {
13639 nsCOMPtr<nsIURI> failedURI;
13640 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
13641 return failedURI.forget();
13642 }
13643 }
13644
13645 nsCOMPtr<nsIURI> uri = GetDocumentURIObject();
13646 if (!uri) {
13647 return nullptr;
13648 }
13649
13650 return uri.forget();
13651 }
13652
GetDocumentReadyForIdle(ErrorResult & aRv)13653 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) {
13654 if (mIsGoingAway) {
13655 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13656 return nullptr;
13657 }
13658
13659 if (!mReadyForIdle) {
13660 nsIGlobalObject* global = GetScopeObject();
13661 if (!global) {
13662 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
13663 return nullptr;
13664 }
13665
13666 mReadyForIdle = Promise::Create(global, aRv);
13667 if (aRv.Failed()) {
13668 return nullptr;
13669 }
13670 }
13671
13672 return mReadyForIdle;
13673 }
13674
MaybeResolveReadyForIdle()13675 void Document::MaybeResolveReadyForIdle() {
13676 IgnoredErrorResult rv;
13677 Promise* readyPromise = GetDocumentReadyForIdle(rv);
13678 if (readyPromise) {
13679 readyPromise->MaybeResolveWithUndefined();
13680 }
13681 }
13682
FeaturePolicy() const13683 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const {
13684 // The policy is created when the document is initialized. We _must_ have a
13685 // policy here even if the featurePolicy pref is off. If this assertion fails,
13686 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad().
13687 MOZ_ASSERT(mFeaturePolicy);
13688 return mFeaturePolicy;
13689 }
13690
GetCommandDispatcher()13691 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() {
13692 // Only chrome documents are allowed to use command dispatcher.
13693 if (!nsContentUtils::IsChromeDoc(this)) {
13694 return nullptr;
13695 }
13696 if (!mCommandDispatcher) {
13697 // Create our command dispatcher and hook it up.
13698 mCommandDispatcher = new nsXULCommandDispatcher(this);
13699 }
13700 return mCommandDispatcher;
13701 }
13702
InitializeXULBroadcastManager()13703 void Document::InitializeXULBroadcastManager() {
13704 if (mXULBroadcastManager) {
13705 return;
13706 }
13707 mXULBroadcastManager = new XULBroadcastManager(this);
13708 }
13709
13710 namespace {
13711
13712 class DevToolsMutationObserver final : public nsStubMutationObserver {
13713 NS_DECL_ISUPPORTS
13714 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
13715 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
13716 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
13717
13718 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
13719 // relies on the event firing _before_ the removal happens.
13720 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
13721
13722 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
13723 // data changes right now (maybe intentionally?).
13724 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
13725
13726 DevToolsMutationObserver() = default;
13727
13728 private:
13729 void FireEvent(nsINode* aTarget, const nsAString& aType);
13730
13731 ~DevToolsMutationObserver() = default;
13732 };
13733
NS_IMPL_ISUPPORTS(DevToolsMutationObserver,nsIMutationObserver)13734 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
13735
13736 void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
13737 const nsAString& aType) {
13738 if (aTarget->ChromeOnlyAccess()) {
13739 return;
13740 }
13741 (new AsyncEventDispatcher(aTarget, aType, CanBubble::eNo,
13742 ChromeOnlyDispatch::eYes, Composed::eYes))
13743 ->RunDOMEventWhenSafe();
13744 }
13745
AttributeChanged(Element * aElement,int32_t aNamespaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)13746 void DevToolsMutationObserver::AttributeChanged(Element* aElement,
13747 int32_t aNamespaceID,
13748 nsAtom* aAttribute,
13749 int32_t aModType,
13750 const nsAttrValue* aOldValue) {
13751 FireEvent(aElement, u"devtoolsattrmodified"_ns);
13752 }
13753
ContentAppended(nsIContent * aFirstNewContent)13754 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
13755 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
13756 ContentInserted(c);
13757 }
13758 }
13759
ContentInserted(nsIContent * aChild)13760 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
13761 FireEvent(aChild, u"devtoolschildinserted"_ns);
13762 }
13763
13764 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
13765
13766 } // namespace
13767
SetDevToolsWatchingDOMMutations(bool aValue)13768 void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
13769 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
13770 return;
13771 }
13772 mDevToolsWatchingDOMMutations = aValue;
13773 if (aValue) {
13774 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
13775 sDevToolsMutationObserver = new DevToolsMutationObserver();
13776 ClearOnShutdown(&sDevToolsMutationObserver);
13777 }
13778 AddMutationObserver(sDevToolsMutationObserver);
13779 } else if (sDevToolsMutationObserver) {
13780 RemoveMutationObserver(sDevToolsMutationObserver);
13781 }
13782 }
13783
MaybeWarnAboutZoom()13784 void Document::MaybeWarnAboutZoom() {
13785 if (mHasWarnedAboutZoom) {
13786 return;
13787 }
13788 const bool usedZoom = Servo_IsPropertyIdRecordedInUseCounter(
13789 mStyleUseCounters.get(), eCSSProperty_zoom);
13790 if (!usedZoom) {
13791 return;
13792 }
13793
13794 mHasWarnedAboutZoom = true;
13795 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
13796 this, nsContentUtils::eLAYOUT_PROPERTIES,
13797 "ZoomPropertyWarning");
13798 }
13799
Children()13800 nsIHTMLCollection* Document::Children() {
13801 if (!mChildrenCollection) {
13802 mChildrenCollection =
13803 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk,
13804 nsGkAtoms::_asterisk, false);
13805 }
13806
13807 return mChildrenCollection;
13808 }
13809
ChildElementCount()13810 uint32_t Document::ChildElementCount() { return Children()->Length(); }
13811
13812 // Singleton class to manage the list of fullscreen documents which are the
13813 // root of a branch which contains fullscreen documents. We maintain this list
13814 // so that we can easily exit all windows from fullscreen when the user
13815 // presses the escape key.
13816 class FullscreenRoots {
13817 public:
13818 // Adds the root of given document to the manager. Calling this method
13819 // with a document whose root is already contained has no effect.
13820 static void Add(Document* aDoc);
13821
13822 // Iterates over every root in the root list, and calls aFunction, passing
13823 // each root once to aFunction. It is safe to call Add() and Remove() while
13824 // iterating over the list (i.e. in aFunction). Documents that are removed
13825 // from the manager during traversal are not traversed, and documents that
13826 // are added to the manager during traversal are also not traversed.
13827 static void ForEach(void (*aFunction)(Document* aDoc));
13828
13829 // Removes the root of a specific document from the manager.
13830 static void Remove(Document* aDoc);
13831
13832 // Returns true if all roots added to the list have been removed.
13833 static bool IsEmpty();
13834
13835 private:
13836 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots)
13837 MOZ_COUNTED_DTOR(FullscreenRoots)
13838
13839 enum { NotFound = uint32_t(-1) };
13840 // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
13841 static uint32_t Find(Document* aRoot);
13842
13843 // Returns true if aRoot is in the list of fullscreen roots.
13844 static bool Contains(Document* aRoot);
13845
13846 // Singleton instance of the FullscreenRoots. This is instantiated when a
13847 // root is added, and it is deleted when the last root is removed.
13848 static FullscreenRoots* sInstance;
13849
13850 // List of weak pointers to roots.
13851 nsTArray<nsWeakPtr> mRoots;
13852 };
13853
13854 FullscreenRoots* FullscreenRoots::sInstance = nullptr;
13855
13856 /* static */
ForEach(void (* aFunction)(Document * aDoc))13857 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) {
13858 if (!sInstance) {
13859 return;
13860 }
13861 // Create a copy of the roots array, and iterate over the copy. This is so
13862 // that if an element is removed from mRoots we don't mess up our iteration.
13863 nsTArray<nsWeakPtr> roots(sInstance->mRoots.Clone());
13864 // Call aFunction on all entries.
13865 for (uint32_t i = 0; i < roots.Length(); i++) {
13866 nsCOMPtr<Document> root = do_QueryReferent(roots[i]);
13867 // Check that the root isn't in the manager. This is so that new additions
13868 // while we were running don't get traversed.
13869 if (root && FullscreenRoots::Contains(root)) {
13870 aFunction(root);
13871 }
13872 }
13873 }
13874
13875 /* static */
Contains(Document * aRoot)13876 bool FullscreenRoots::Contains(Document* aRoot) {
13877 return FullscreenRoots::Find(aRoot) != NotFound;
13878 }
13879
13880 /* static */
Add(Document * aDoc)13881 void FullscreenRoots::Add(Document* aDoc) {
13882 nsCOMPtr<Document> root =
13883 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
13884 if (!FullscreenRoots::Contains(root)) {
13885 if (!sInstance) {
13886 sInstance = new FullscreenRoots();
13887 }
13888 sInstance->mRoots.AppendElement(do_GetWeakReference(root));
13889 }
13890 }
13891
13892 /* static */
Find(Document * aRoot)13893 uint32_t FullscreenRoots::Find(Document* aRoot) {
13894 if (!sInstance) {
13895 return NotFound;
13896 }
13897 nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
13898 for (uint32_t i = 0; i < roots.Length(); i++) {
13899 nsCOMPtr<Document> otherRoot(do_QueryReferent(roots[i]));
13900 if (otherRoot == aRoot) {
13901 return i;
13902 }
13903 }
13904 return NotFound;
13905 }
13906
13907 /* static */
Remove(Document * aDoc)13908 void FullscreenRoots::Remove(Document* aDoc) {
13909 nsCOMPtr<Document> root =
13910 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
13911 uint32_t index = Find(root);
13912 NS_ASSERTION(index != NotFound,
13913 "Should only try to remove roots which are still added!");
13914 if (index == NotFound || !sInstance) {
13915 return;
13916 }
13917 sInstance->mRoots.RemoveElementAt(index);
13918 if (sInstance->mRoots.IsEmpty()) {
13919 delete sInstance;
13920 sInstance = nullptr;
13921 }
13922 }
13923
13924 /* static */
IsEmpty()13925 bool FullscreenRoots::IsEmpty() { return !sInstance; }
13926
13927 // Any fullscreen change waiting for the widget to finish transition
13928 // is queued here. This is declared static instead of a member of
13929 // Document because in the majority of time, there would be at most
13930 // one document requesting or exiting fullscreen. We shouldn't waste
13931 // the space to hold for it in every document.
13932 class PendingFullscreenChangeList {
13933 public:
13934 PendingFullscreenChangeList() = delete;
13935
13936 template <typename T>
Add(UniquePtr<T> aChange)13937 static void Add(UniquePtr<T> aChange) {
13938 sList.insertBack(aChange.release());
13939 }
13940
GetLast()13941 static const FullscreenChange* GetLast() { return sList.getLast(); }
13942
13943 enum IteratorOption {
13944 // When we are committing fullscreen changes or preparing for
13945 // that, we generally want to iterate all requests in the same
13946 // window with eDocumentsWithSameRoot option.
13947 eDocumentsWithSameRoot,
13948 // If we are removing a document from the tree, we would only
13949 // want to remove the requests from the given document and its
13950 // descendants. For that case, use eInclusiveDescendants.
13951 eInclusiveDescendants
13952 };
13953
13954 template <typename T>
13955 class Iterator {
13956 public:
Iterator(Document * aDoc,IteratorOption aOption)13957 explicit Iterator(Document* aDoc, IteratorOption aOption)
13958 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) {
13959 if (mCurrent) {
13960 if (aDoc->GetBrowsingContext()) {
13961 mRootBCForIteration = aDoc->GetBrowsingContext();
13962 if (aOption == eDocumentsWithSameRoot) {
13963 RefPtr<BrowsingContext> bc =
13964 GetParentIgnoreChromeBoundary(mRootBCForIteration);
13965 while (bc) {
13966 mRootBCForIteration = bc;
13967 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration);
13968 }
13969 }
13970 }
13971 SkipToNextMatch();
13972 }
13973 }
13974
TakeAndNext()13975 UniquePtr<T> TakeAndNext() {
13976 auto thisChange = TakeAndNextInternal();
13977 SkipToNextMatch();
13978 return thisChange;
13979 }
AtEnd() const13980 bool AtEnd() const { return mCurrent == nullptr; }
13981
13982 private:
GetParentIgnoreChromeBoundary(BrowsingContext * aBC)13983 already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
13984 BrowsingContext* aBC) {
13985 // Chrome BrowsingContexts are only available in the parent process, so if
13986 // we're in a content process, we only worry about the context tree.
13987 if (XRE_IsParentProcess()) {
13988 return aBC->Canonical()->GetParentCrossChromeBoundary();
13989 }
13990 return do_AddRef(aBC->GetParent());
13991 }
13992
TakeAndNextInternal()13993 UniquePtr<T> TakeAndNextInternal() {
13994 FullscreenChange* thisChange = mCurrent;
13995 MOZ_ASSERT(thisChange->Type() == T::kType);
13996 mCurrent = mCurrent->removeAndGetNext();
13997 return WrapUnique(static_cast<T*>(thisChange));
13998 }
SkipToNextMatch()13999 void SkipToNextMatch() {
14000 while (mCurrent) {
14001 if (mCurrent->Type() == T::kType) {
14002 RefPtr<BrowsingContext> bc =
14003 mCurrent->Document()->GetBrowsingContext();
14004 if (!bc) {
14005 // Always automatically drop fullscreen changes which are
14006 // from a document detached from the doc shell.
14007 UniquePtr<T> change = TakeAndNextInternal();
14008 change->MayRejectPromise("Document is not active");
14009 continue;
14010 }
14011 while (bc && bc != mRootBCForIteration) {
14012 bc = GetParentIgnoreChromeBoundary(bc);
14013 }
14014 if (bc) {
14015 break;
14016 }
14017 }
14018 // The current one either don't have matched type, or isn't
14019 // inside the given subtree, so skip this item.
14020 mCurrent = mCurrent->getNext();
14021 }
14022 }
14023
14024 FullscreenChange* mCurrent;
14025 RefPtr<BrowsingContext> mRootBCForIteration;
14026 };
14027
14028 private:
14029 static LinkedList<FullscreenChange> sList;
14030 };
14031
14032 /* static */
14033 LinkedList<FullscreenChange> PendingFullscreenChangeList::sList;
14034
GetFullscreenRoot()14035 Document* Document::GetFullscreenRoot() {
14036 nsCOMPtr<Document> root = do_QueryReferent(mFullscreenRoot);
14037 return root;
14038 }
14039
CountFullscreenElements() const14040 size_t Document::CountFullscreenElements() const {
14041 size_t count = 0;
14042 for (const nsWeakPtr& ptr : mTopLayer) {
14043 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14044 if (elem->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
14045 count++;
14046 }
14047 }
14048 }
14049 return count;
14050 }
14051
SetFullscreenRoot(Document * aRoot)14052 void Document::SetFullscreenRoot(Document* aRoot) {
14053 mFullscreenRoot = do_GetWeakReference(aRoot);
14054 }
14055
TryCancelDialog()14056 void Document::TryCancelDialog() {
14057 // Check if the document is blocked by modal dialog
14058 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14059 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14060 if (HTMLDialogElement* dialog =
14061 HTMLDialogElement::FromNodeOrNull(element)) {
14062 dialog->QueueCancelDialog();
14063 break;
14064 }
14065 }
14066 }
14067
ExitFullscreen(ErrorResult & aRv)14068 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) {
14069 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv);
14070 RefPtr<Promise> promise = exit->GetPromise();
14071 RestorePreviousFullscreenState(std::move(exit));
14072 return promise.forget();
14073 }
14074
AskWindowToExitFullscreen(Document * aDoc)14075 static void AskWindowToExitFullscreen(Document* aDoc) {
14076 if (XRE_GetProcessType() == GeckoProcessType_Content) {
14077 nsContentUtils::DispatchEventOnlyToChrome(
14078 aDoc, ToSupports(aDoc), u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes,
14079 Cancelable::eNo, /* DefaultAction */ nullptr);
14080 } else {
14081 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
14082 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
14083 }
14084 }
14085 }
14086
14087 class nsCallExitFullscreen : public Runnable {
14088 public:
nsCallExitFullscreen(Document * aDoc)14089 explicit nsCallExitFullscreen(Document* aDoc)
14090 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {}
14091
Run()14092 NS_IMETHOD Run() final {
14093 if (!mDoc) {
14094 FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
14095 } else {
14096 AskWindowToExitFullscreen(mDoc);
14097 }
14098 return NS_OK;
14099 }
14100
14101 private:
14102 nsCOMPtr<Document> mDoc;
14103 };
14104
14105 /* static */
AsyncExitFullscreen(Document * aDoc)14106 void Document::AsyncExitFullscreen(Document* aDoc) {
14107 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14108 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc);
14109 if (aDoc) {
14110 aDoc->Dispatch(TaskCategory::Other, exit.forget());
14111 } else {
14112 NS_DispatchToCurrentThread(exit.forget());
14113 }
14114 }
14115
CountFullscreenSubDocuments(Document & aDoc)14116 static uint32_t CountFullscreenSubDocuments(Document& aDoc) {
14117 uint32_t count = 0;
14118 // FIXME(emilio): Should this be recursive and dig into our nested subdocs?
14119 auto subDoc = [&count](Document& aSubDoc) {
14120 if (aSubDoc.GetUnretargetedFullScreenElement()) {
14121 count++;
14122 }
14123 return CallState::Continue;
14124 };
14125 aDoc.EnumerateSubDocuments(subDoc);
14126 return count;
14127 }
14128
IsFullscreenLeaf()14129 bool Document::IsFullscreenLeaf() {
14130 // A fullscreen leaf document is fullscreen, and has no fullscreen
14131 // subdocuments.
14132 if (!GetUnretargetedFullScreenElement()) {
14133 return false;
14134 }
14135 return CountFullscreenSubDocuments(*this) == 0;
14136 }
14137
GetFullscreenLeaf(Document & aDoc)14138 static Document* GetFullscreenLeaf(Document& aDoc) {
14139 if (aDoc.IsFullscreenLeaf()) {
14140 return &aDoc;
14141 }
14142 if (!aDoc.GetUnretargetedFullScreenElement()) {
14143 return nullptr;
14144 }
14145 Document* leaf = nullptr;
14146 auto recurse = [&leaf](Document& aSubDoc) {
14147 leaf = GetFullscreenLeaf(aSubDoc);
14148 return leaf ? CallState::Stop : CallState::Continue;
14149 };
14150 aDoc.EnumerateSubDocuments(recurse);
14151 return leaf;
14152 }
14153
GetFullscreenLeaf(Document * aDoc)14154 static Document* GetFullscreenLeaf(Document* aDoc) {
14155 if (Document* leaf = GetFullscreenLeaf(*aDoc)) {
14156 return leaf;
14157 }
14158 // Otherwise we could be either in a non-fullscreen doc tree, or we're
14159 // below the fullscreen doc. Start the search from the root.
14160 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc);
14161 return GetFullscreenLeaf(*root);
14162 }
14163
ResetFullscreen(Document & aDocument)14164 static CallState ResetFullscreen(Document& aDocument) {
14165 if (Element* fsElement = aDocument.GetUnretargetedFullScreenElement()) {
14166 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
14167 "Should have at most 1 fullscreen subdocument.");
14168 aDocument.CleanupFullscreenState();
14169 NS_ASSERTION(!aDocument.GetUnretargetedFullScreenElement(),
14170 "Should reset fullscreen");
14171 DispatchFullscreenChange(aDocument, fsElement);
14172 aDocument.EnumerateSubDocuments(ResetFullscreen);
14173 }
14174 return CallState::Continue;
14175 }
14176
14177 // Since Document::ExitFullscreenInDocTree() could be called from
14178 // Element::UnbindFromTree() where it is not safe to synchronously run
14179 // script. This runnable is the script part of that function.
14180 class ExitFullscreenScriptRunnable : public Runnable {
14181 public:
ExitFullscreenScriptRunnable(Document * aRoot,Document * aLeaf)14182 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf)
14183 : mozilla::Runnable("ExitFullscreenScriptRunnable"),
14184 mRoot(aRoot),
14185 mLeaf(aLeaf) {}
14186
Run()14187 NS_IMETHOD Run() override {
14188 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf
14189 // document since we want this event to follow the same path that
14190 // MozDOMFullscreen:Entered was dispatched.
14191 nsContentUtils::DispatchEventOnlyToChrome(
14192 mLeaf, ToSupports(mLeaf), u"MozDOMFullscreen:Exited"_ns,
14193 CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
14194 // Ensure the window exits fullscreen.
14195 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) {
14196 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen,
14197 false);
14198 }
14199 return NS_OK;
14200 }
14201
14202 private:
14203 nsCOMPtr<Document> mRoot;
14204 nsCOMPtr<Document> mLeaf;
14205 };
14206
14207 /* static */
ExitFullscreenInDocTree(Document * aMaybeNotARootDoc)14208 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) {
14209 MOZ_ASSERT(aMaybeNotARootDoc);
14210
14211 // Unlock the pointer
14212 PointerLockManager::Unlock();
14213
14214 // Resolve all promises which waiting for exit fullscreen.
14215 PendingFullscreenChangeList::Iterator<FullscreenExit> iter(
14216 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14217 while (!iter.AtEnd()) {
14218 UniquePtr<FullscreenExit> exit = iter.TakeAndNext();
14219 exit->MayResolvePromise();
14220 }
14221
14222 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot();
14223 if (!root || !root->GetUnretargetedFullScreenElement()) {
14224 // If a document was detached before exiting from fullscreen, it is
14225 // possible that the root had left fullscreen state. In this case,
14226 // we would not get anything from the ResetFullscreen() call. Root's
14227 // not being a fullscreen doc also means the widget should have
14228 // exited fullscreen state. It means even if we do not return here,
14229 // we would actually do nothing below except crashing ourselves via
14230 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
14231 // document.
14232 return;
14233 }
14234
14235 // Record the fullscreen leaf document for MozDOMFullscreen:Exited.
14236 // See ExitFullscreenScriptRunnable::Run for details. We have to
14237 // record it here because we don't have such information after we
14238 // reset the fullscreen state below.
14239 Document* fullscreenLeaf = GetFullscreenLeaf(root);
14240
14241 // Walk the tree of fullscreen documents, and reset their fullscreen state.
14242 ResetFullscreen(*root);
14243
14244 NS_ASSERTION(!root->GetUnretargetedFullScreenElement(),
14245 "Fullscreen root should no longer be a fullscreen doc...");
14246
14247 // Move the top-level window out of fullscreen mode.
14248 FullscreenRoots::Remove(root);
14249
14250 nsContentUtils::AddScriptRunner(
14251 new ExitFullscreenScriptRunnable(root, fullscreenLeaf));
14252 }
14253
DispatchFullscreenNewOriginEvent(Document * aDoc)14254 static void DispatchFullscreenNewOriginEvent(Document* aDoc) {
14255 RefPtr<AsyncEventDispatcher> asyncDispatcher =
14256 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns,
14257 CanBubble::eYes, ChromeOnlyDispatch::eYes);
14258 asyncDispatcher->PostDOMEvent();
14259 }
14260
RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit)14261 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) {
14262 NS_ASSERTION(
14263 !GetUnretargetedFullScreenElement() || !FullscreenRoots::IsEmpty(),
14264 "Should have at least 1 fullscreen root when fullscreen!");
14265
14266 if (!GetWindow()) {
14267 aExit->MayRejectPromise("No active window");
14268 return;
14269 }
14270 if (!GetUnretargetedFullScreenElement() || FullscreenRoots::IsEmpty()) {
14271 aExit->MayRejectPromise("Not in fullscreen mode");
14272 return;
14273 }
14274
14275 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this);
14276 AutoTArray<Element*, 8> exitElements;
14277
14278 Document* doc = fullScreenDoc;
14279 // Collect all subdocuments.
14280 for (; doc != this; doc = doc->GetInProcessParentDocument()) {
14281 Element* fsElement = doc->GetUnretargetedFullScreenElement();
14282 MOZ_ASSERT(fsElement,
14283 "Parent document of "
14284 "a fullscreen document without fullscreen element?");
14285 exitElements.AppendElement(fsElement);
14286 }
14287 MOZ_ASSERT(doc == this, "Must have reached this doc");
14288 // Collect all ancestor documents which we are going to change.
14289 for (; doc; doc = doc->GetInProcessParentDocument()) {
14290 Element* fsElement = doc->GetUnretargetedFullScreenElement();
14291 MOZ_ASSERT(fsElement,
14292 "Ancestor of fullscreen document must also be in fullscreen");
14293 if (doc != this) {
14294 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) {
14295 if (iframe->FullscreenFlag()) {
14296 // If this is an iframe, and it explicitly requested
14297 // fullscreen, don't rollback it automatically.
14298 break;
14299 }
14300 }
14301 }
14302 exitElements.AppendElement(fsElement);
14303 if (doc->CountFullscreenElements() > 1) {
14304 break;
14305 }
14306 }
14307
14308 Document* lastDoc = exitElements.LastElement()->OwnerDoc();
14309 size_t fullscreenCount = lastDoc->CountFullscreenElements();
14310 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) {
14311 // If we are fully exiting fullscreen, don't touch anything here,
14312 // just wait for the window to get out from fullscreen first.
14313 PendingFullscreenChangeList::Add(std::move(aExit));
14314 AskWindowToExitFullscreen(this);
14315 return;
14316 }
14317
14318 // If fullscreen mode is updated the pointer should be unlocked
14319 PointerLockManager::Unlock();
14320 // All documents listed in the array except the last one are going to
14321 // completely exit from the fullscreen state.
14322 for (auto i : IntegerRange(exitElements.Length() - 1)) {
14323 exitElements[i]->OwnerDoc()->CleanupFullscreenState();
14324 }
14325 // The last document will either rollback one fullscreen element, or
14326 // completely exit from the fullscreen state as well.
14327 Document* newFullscreenDoc;
14328 if (fullscreenCount > 1) {
14329 lastDoc->UnsetFullscreenElement();
14330 newFullscreenDoc = lastDoc;
14331 } else {
14332 lastDoc->CleanupFullscreenState();
14333 newFullscreenDoc = lastDoc->GetInProcessParentDocument();
14334 }
14335 // Dispatch the fullscreenchange event to all document listed. Note
14336 // that the loop order is reversed so that events are dispatched in
14337 // the tree order as indicated in the spec.
14338 for (Element* e : Reversed(exitElements)) {
14339 DispatchFullscreenChange(*e->OwnerDoc(), e);
14340 }
14341 aExit->MayResolvePromise();
14342
14343 MOZ_ASSERT(newFullscreenDoc,
14344 "If we were going to exit from fullscreen on "
14345 "all documents in this doctree, we should've asked the window to "
14346 "exit first instead of reaching here.");
14347 if (fullScreenDoc != newFullscreenDoc &&
14348 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) {
14349 // We've popped so enough off the stack that we've rolled back to
14350 // a fullscreen element in a parent document. If this document is
14351 // cross origin, dispatch an event to chrome so it knows to show
14352 // the warning UI.
14353 DispatchFullscreenNewOriginEvent(newFullscreenDoc);
14354 }
14355 }
14356
14357 class nsCallRequestFullscreen : public Runnable {
14358 public:
nsCallRequestFullscreen(UniquePtr<FullscreenRequest> aRequest)14359 explicit nsCallRequestFullscreen(UniquePtr<FullscreenRequest> aRequest)
14360 : mozilla::Runnable("nsCallRequestFullscreen"),
14361 mRequest(std::move(aRequest)) {}
14362
Run()14363 NS_IMETHOD Run() override {
14364 Document* doc = mRequest->Document();
14365 doc->RequestFullscreen(std::move(mRequest));
14366 return NS_OK;
14367 }
14368
14369 UniquePtr<FullscreenRequest> mRequest;
14370 };
14371
AsyncRequestFullscreen(UniquePtr<FullscreenRequest> aRequest)14372 void Document::AsyncRequestFullscreen(UniquePtr<FullscreenRequest> aRequest) {
14373 // Request fullscreen asynchronously.
14374 MOZ_RELEASE_ASSERT(NS_IsMainThread());
14375 nsCOMPtr<nsIRunnable> event =
14376 new nsCallRequestFullscreen(std::move(aRequest));
14377 Dispatch(TaskCategory::Other, event.forget());
14378 }
14379
UpdateViewportScrollbarOverrideForFullscreen(Document * aDoc)14380 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
14381 if (nsPresContext* presContext = aDoc->GetPresContext()) {
14382 presContext->UpdateViewportScrollStylesOverride();
14383 }
14384 }
14385
NotifyFullScreenChangedForMediaElement(Element * aElement,bool aIsInFullScreen)14386 static void NotifyFullScreenChangedForMediaElement(Element* aElement,
14387 bool aIsInFullScreen) {
14388 // When a media element enters the fullscreen, we would like to notify that
14389 // to the media controller in order to update its status.
14390 if (!aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
14391 return;
14392 }
14393 HTMLMediaElement* mediaElem = HTMLMediaElement::FromNodeOrNull(aElement);
14394 mediaElem->NotifyFullScreenChanged();
14395 }
14396
ClearFullscreenStateOnElement(Element * aElement)14397 static void ClearFullscreenStateOnElement(Element* aElement) {
14398 // Remove styles from existing top element.
14399 EventStateManager::SetFullscreenState(aElement, false);
14400 NotifyFullScreenChangedForMediaElement(aElement, false);
14401 // Reset iframe fullscreen flag.
14402 if (aElement->IsHTMLElement(nsGkAtoms::iframe)) {
14403 static_cast<HTMLIFrameElement*>(aElement)->SetFullscreenFlag(false);
14404 }
14405 }
14406
CleanupFullscreenState()14407 void Document::CleanupFullscreenState() {
14408 // Iterate the top layer and clear the fullscreen states.
14409 // Since we also need to clear the fullscreen-ancestor state, and
14410 // currently fullscreen elements can only be placed in hierarchy
14411 // order in the stack, reversely iterating the stack could be more
14412 // efficient. NOTE that fullscreen-ancestor state would be removed
14413 // in bug 1199529, and the elements may not in hierarchy order
14414 // after bug 1195213.
14415 mTopLayer.RemoveElementsBy([&](const nsWeakPtr& weakPtr) {
14416 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14417 if (!element || !element->IsInComposedDoc() ||
14418 element->OwnerDoc() != this) {
14419 return true;
14420 }
14421
14422 if (element->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
14423 ClearFullscreenStateOnElement(element);
14424 return true;
14425 }
14426 return false;
14427 });
14428
14429 mFullscreenRoot = nullptr;
14430
14431 // Restore the zoom level that was in place prior to entering fullscreen.
14432 if (PresShell* presShell = GetPresShell()) {
14433 if (presShell->GetMobileViewportManager()) {
14434 presShell->SetResolutionAndScaleTo(
14435 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore);
14436 }
14437 }
14438
14439 UpdateViewportScrollbarOverrideForFullscreen(this);
14440 }
14441
UnsetFullscreenElement()14442 void Document::UnsetFullscreenElement() {
14443 Element* removedElement = TopLayerPop([](Element* element) -> bool {
14444 return element->State().HasState(NS_EVENT_STATE_FULLSCREEN);
14445 });
14446
14447 MOZ_ASSERT(removedElement->State().HasState(NS_EVENT_STATE_FULLSCREEN));
14448 ClearFullscreenStateOnElement(removedElement);
14449 UpdateViewportScrollbarOverrideForFullscreen(this);
14450 }
14451
SetFullscreenElement(Element * aElement)14452 void Document::SetFullscreenElement(Element* aElement) {
14453 TopLayerPush(aElement);
14454 EventStateManager::SetFullscreenState(aElement, true);
14455 NotifyFullScreenChangedForMediaElement(aElement, true);
14456 UpdateViewportScrollbarOverrideForFullscreen(this);
14457 }
14458
TopLayerPush(Element * aElement)14459 void Document::TopLayerPush(Element* aElement) {
14460 NS_ASSERTION(aElement, "Must pass non-null to TopLayerPush()");
14461 auto predictFunc = [&aElement](Element* element) {
14462 return element == aElement;
14463 };
14464 TopLayerPop(predictFunc);
14465
14466 mTopLayer.AppendElement(do_GetWeakReference(aElement));
14467 NS_ASSERTION(GetTopLayerTop() == aElement, "Should match");
14468 }
14469
SetBlockedByModalDialog(HTMLDialogElement & aDialogElement)14470 void Document::SetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
14471 Element* root = GetRootElement();
14472 MOZ_RELEASE_ASSERT(root, "dialog in document without root?");
14473
14474 // Add inert to the root element so that the inertness is
14475 // applied to the entire document. Since the modal dialog
14476 // also inherits the inertness, adding
14477 // NS_EVENT_STATE_TOPMOST_MODAL_DIALOG to remove the inertness
14478 // explicitly.
14479 root->AddStates(NS_EVENT_STATE_MOZINERT);
14480 aDialogElement.AddStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
14481
14482 // It's possible that there's another modal dialog has opened
14483 // previously which doesn't have the inertness (because we've
14484 // removed the inertness explicitly). Since a
14485 // new modal dialog is opened, we need to grant the inertness
14486 // to the previous one.
14487 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14488 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14489 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14490 if (dialog != &aDialogElement) {
14491 dialog->RemoveStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
14492 // It's ok to exit the loop as only one modal dialog should
14493 // have the state
14494 break;
14495 }
14496 }
14497 }
14498 }
14499
UnsetBlockedByModalDialog(HTMLDialogElement & aDialogElement)14500 void Document::UnsetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
14501 aDialogElement.RemoveStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
14502
14503 // The document could still be blocked by another modal dialog.
14504 // We need to remove the inertness from this modal dialog.
14505 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14506 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14507 if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
14508 if (dialog != &aDialogElement) {
14509 dialog->AddStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
14510 // Return here because we want to keep the inertness for the
14511 // root element as the document is still blocked by a modal
14512 // dialog
14513 return;
14514 }
14515 }
14516 }
14517
14518 Element* root = GetRootElement();
14519 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) {
14520 root->RemoveStates(NS_EVENT_STATE_MOZINERT);
14521 }
14522 }
14523
TopLayerPop(FunctionRef<bool (Element *)> aPredicateFunc)14524 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicateFunc) {
14525 if (mTopLayer.IsEmpty()) {
14526 return nullptr;
14527 }
14528
14529 // Remove the topmost element that qualifies aPredicate; This
14530 // is required is because the top layer contains not only
14531 // fullscreen elements, but also dialog elements.
14532 Element* removedElement = nullptr;
14533 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
14534 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
14535 if (element && aPredicateFunc(element)) {
14536 removedElement = element;
14537 mTopLayer.RemoveElementAt(i);
14538 break;
14539 }
14540 }
14541
14542 // Pop from the stack null elements (references to elements which have
14543 // been GC'd since they were added to the stack) and elements which are
14544 // no longer in this document.
14545 //
14546 // FIXME(emilio): If this loop does something, it'd violate the assertions
14547 // from GetTopLayerTop()... What gives?
14548 while (!mTopLayer.IsEmpty()) {
14549 Element* element = GetTopLayerTop();
14550 if (!element || element->GetComposedDoc() != this) {
14551 mTopLayer.RemoveLastElement();
14552 } else {
14553 // The top element of the stack is now an in-doc element. Return here.
14554 break;
14555 }
14556 }
14557
14558 return removedElement;
14559 }
14560
GetWireframe(bool aIncludeNodes,Nullable<Wireframe> & aWireframe)14561 void Document::GetWireframe(bool aIncludeNodes,
14562 Nullable<Wireframe>& aWireframe) {
14563 FlushPendingNotifications(FlushType::Layout);
14564 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe);
14565 }
14566
GetWireframeWithoutFlushing(bool aIncludeNodes,Nullable<Wireframe> & aWireframe)14567 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
14568 Nullable<Wireframe>& aWireframe) {
14569 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
14570 using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
14571
14572 PresShell* shell = GetPresShell();
14573 if (!shell) {
14574 return;
14575 }
14576
14577 nsPresContext* pc = shell->GetPresContext();
14578 if (!pc) {
14579 return;
14580 }
14581
14582 nsIFrame* rootFrame = shell->GetRootFrame();
14583 if (!rootFrame) {
14584 return;
14585 }
14586
14587 auto& wireframe = aWireframe.SetValue();
14588 nsStyleUtil::GetSerializedColorValue(shell->GetCanvasBackground(),
14589 wireframe.mCanvasBackground.Construct());
14590
14591 FrameForPointOptions options;
14592 options.mBits += FrameForPointOption::IgnoreCrossDoc;
14593 options.mBits += FrameForPointOption::IgnorePaintSuppression;
14594 options.mBits += FrameForPointOption::OnlyVisible;
14595
14596 AutoTArray<nsIFrame*, 32> frames;
14597 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout};
14598 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
14599 options);
14600
14601 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
14602 // something perhaps, but seems hard / like it'd involve at least some extra
14603 // copying around, since they don't outlive GetFramesForArea.
14604 auto& rects = wireframe.mRects.Construct();
14605 if (!rects.SetCapacity(frames.Length(), fallible)) {
14606 return;
14607 }
14608 for (nsIFrame* frame : Reversed(frames)) {
14609 // Can't really fail because SetCapacity succeeded.
14610 auto& taggedRect = *rects.AppendElement(fallible);
14611 const auto r =
14612 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
14613 frame, frame->GetRectRelativeToSelf(), relativeTo));
14614 if (aIncludeNodes) {
14615 if (nsIContent* c = frame->GetContent()) {
14616 taggedRect.mNode.Construct(c);
14617 }
14618 }
14619 taggedRect.mX = r.x;
14620 taggedRect.mY = r.y;
14621 taggedRect.mWidth = r.width;
14622 taggedRect.mHeight = r.height;
14623 taggedRect.mType.Construct() = [&] {
14624 if (frame->IsTextFrame()) {
14625 nsStyleUtil::GetSerializedColorValue(
14626 frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
14627 taggedRect.mColor.Construct());
14628 return WireframeRectType::Text;
14629 }
14630 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
14631 return WireframeRectType::Image;
14632 }
14633 if (frame->IsThemed()) {
14634 return WireframeRectType::Background;
14635 }
14636 bool drawImage = false;
14637 bool drawColor = false;
14638 const nscolor color = nsCSSRendering::DetermineBackgroundColor(
14639 pc, frame->Style(), frame, drawImage, drawColor);
14640 if (drawImage &&
14641 !frame->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
14642 return WireframeRectType::Image;
14643 }
14644 if (drawColor) {
14645 nsStyleUtil::GetSerializedColorValue(color,
14646 taggedRect.mColor.Construct());
14647 return WireframeRectType::Background;
14648 }
14649 return WireframeRectType::Unknown;
14650 }();
14651 }
14652 }
14653
GetTopLayerTop()14654 Element* Document::GetTopLayerTop() {
14655 if (mTopLayer.IsEmpty()) {
14656 return nullptr;
14657 }
14658 uint32_t last = mTopLayer.Length() - 1;
14659 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
14660 NS_ASSERTION(element, "Should have a top layer element!");
14661 NS_ASSERTION(element->IsInComposedDoc(),
14662 "Top layer element should be in doc");
14663 NS_ASSERTION(element->OwnerDoc() == this,
14664 "Top layer element should be in this doc");
14665 return element;
14666 }
14667
GetUnretargetedFullScreenElement() const14668 Element* Document::GetUnretargetedFullScreenElement() const {
14669 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
14670 nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
14671 // Per spec, the fullscreen element is the topmost element in the document’s
14672 // top layer whose fullscreen flag is set, if any, and null otherwise.
14673 if (element && element->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
14674 return element;
14675 }
14676 }
14677 return nullptr;
14678 }
14679
GetTopLayer() const14680 nsTArray<Element*> Document::GetTopLayer() const {
14681 nsTArray<Element*> elements;
14682 for (const nsWeakPtr& ptr : mTopLayer) {
14683 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
14684 elements.AppendElement(elem);
14685 }
14686 }
14687 return elements;
14688 }
14689
14690 // Returns true if aDoc is in the focused tab in the active window.
IsInActiveTab(Document * aDoc)14691 bool IsInActiveTab(Document* aDoc) {
14692 BrowsingContext* bc = aDoc->GetBrowsingContext();
14693 if (!bc) {
14694 return false;
14695 }
14696
14697 if (!bc->IsActive()) {
14698 return false;
14699 }
14700
14701 nsFocusManager* fm = nsFocusManager::GetFocusManager();
14702 if (!fm) {
14703 return false;
14704 }
14705
14706 if (XRE_IsParentProcess()) {
14707 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy
14708 // by retaining the old code path for the parent process.
14709 nsIDocShell* docshell = aDoc->GetDocShell();
14710 if (!docshell) {
14711 return false;
14712 }
14713 nsCOMPtr<nsIDocShellTreeItem> rootItem;
14714 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
14715 if (!rootItem) {
14716 return false;
14717 }
14718 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
14719 if (!rootWin) {
14720 return false;
14721 }
14722
14723 nsCOMPtr<nsPIDOMWindowOuter> activeWindow;
14724 activeWindow = fm->GetActiveWindow();
14725 if (!activeWindow) {
14726 return false;
14727 }
14728
14729 return activeWindow == rootWin;
14730 }
14731
14732 return fm->GetActiveBrowsingContext() == bc->Top();
14733 }
14734
RemoteFrameFullscreenChanged(Element * aFrameElement)14735 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) {
14736 // Ensure the frame element is the fullscreen element in this document.
14737 // If the frame element is already the fullscreen element in this document,
14738 // this has no effect.
14739 auto request = FullscreenRequest::CreateForRemote(aFrameElement);
14740 RequestFullscreen(std::move(request), XRE_IsContentProcess());
14741 }
14742
RemoteFrameFullscreenReverted()14743 void Document::RemoteFrameFullscreenReverted() {
14744 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this);
14745 RestorePreviousFullscreenState(std::move(exit));
14746 }
14747
HasFullscreenSubDocument(Document & aDoc)14748 static bool HasFullscreenSubDocument(Document& aDoc) {
14749 uint32_t count = CountFullscreenSubDocuments(aDoc);
14750 NS_ASSERTION(count <= 1,
14751 "Fullscreen docs should have at most 1 fullscreen child!");
14752 return count >= 1;
14753 }
14754
14755 // Returns nullptr if a request for Fullscreen API is currently enabled
14756 // in the given document. Returns a static string indicates the reason
14757 // why it is not enabled otherwise.
GetFullscreenError(CallerType aCallerType)14758 const char* Document::GetFullscreenError(CallerType aCallerType) {
14759 if (!StaticPrefs::full_screen_api_enabled()) {
14760 return "FullscreenDeniedDisabled";
14761 }
14762
14763 if (aCallerType == CallerType::System) {
14764 // Chrome code can always use the fullscreen API, provided it's not
14765 // explicitly disabled.
14766 return nullptr;
14767 }
14768
14769 if (!IsVisible()) {
14770 return "FullscreenDeniedHidden";
14771 }
14772
14773 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) {
14774 return "FullscreenDeniedFeaturePolicy";
14775 }
14776
14777 // Ensure that all containing elements are <iframe> and have allowfullscreen
14778 // attribute set.
14779 BrowsingContext* bc = GetBrowsingContext();
14780 if (!bc || !bc->FullscreenAllowed()) {
14781 return "FullscreenDeniedContainerNotAllowed";
14782 }
14783
14784 return nullptr;
14785 }
14786
FullscreenElementReadyCheck(FullscreenRequest & aRequest)14787 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) {
14788 Element* elem = aRequest.Element();
14789 // Strictly speaking, this isn't part of the fullscreen element ready
14790 // check in the spec, but per steps in the spec, when an element which
14791 // is already the fullscreen element requests fullscreen, nothing
14792 // should change and no event should be dispatched, but we still need
14793 // to resolve the returned promise.
14794 Element* fullscreenElement = GetUnretargetedFullScreenElement();
14795 if (elem == fullscreenElement) {
14796 aRequest.MayResolvePromise();
14797 return false;
14798 }
14799 if (!elem->IsInComposedDoc()) {
14800 aRequest.Reject("FullscreenDeniedNotInDocument");
14801 return false;
14802 }
14803 if (elem->OwnerDoc() != this) {
14804 aRequest.Reject("FullscreenDeniedMovedDocument");
14805 return false;
14806 }
14807 if (!GetWindow()) {
14808 aRequest.Reject("FullscreenDeniedLostWindow");
14809 return false;
14810 }
14811 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) {
14812 aRequest.Reject(msg);
14813 return false;
14814 }
14815 if (HasFullscreenSubDocument(*this)) {
14816 aRequest.Reject("FullscreenDeniedSubDocFullScreen");
14817 return false;
14818 }
14819 if (elem->IsHTMLElement(nsGkAtoms::dialog)) {
14820 aRequest.Reject("FullscreenDeniedHTMLDialog");
14821 return false;
14822 }
14823 // XXXsmaug Note, we don't follow the latest fullscreen spec here.
14824 // This whole check could be probably removed.
14825 if (fullscreenElement && !nsContentUtils::ContentIsHostIncludingDescendantOf(
14826 elem, fullscreenElement)) {
14827 // If this document is fullscreen, only grant fullscreen requests from
14828 // a descendant of the current fullscreen element.
14829 aRequest.Reject("FullscreenDeniedNotDescendant");
14830 return false;
14831 }
14832 if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) {
14833 aRequest.Reject("FullscreenDeniedNotFocusedTab");
14834 return false;
14835 }
14836 // Deny requests when a windowed plugin is focused.
14837 nsFocusManager* fm = nsFocusManager::GetFocusManager();
14838 if (!fm) {
14839 NS_WARNING("Failed to retrieve focus manager in fullscreen request.");
14840 aRequest.MayRejectPromise("An unexpected error occurred");
14841 return false;
14842 }
14843 if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(
14844 fm->GetFocusedElement())) {
14845 aRequest.Reject("FullscreenDeniedFocusedPlugin");
14846 return false;
14847 }
14848 return true;
14849 }
14850
GetRootWindow(Document * aDoc)14851 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) {
14852 MOZ_ASSERT(XRE_IsParentProcess());
14853 nsIDocShell* docShell = aDoc->GetDocShell();
14854 if (!docShell) {
14855 return nullptr;
14856 }
14857 nsCOMPtr<nsIDocShellTreeItem> rootItem;
14858 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
14859 return rootItem ? rootItem->GetWindow() : nullptr;
14860 }
14861
ShouldApplyFullscreenDirectly(Document * aDoc,nsPIDOMWindowOuter * aRootWin)14862 static bool ShouldApplyFullscreenDirectly(Document* aDoc,
14863 nsPIDOMWindowOuter* aRootWin) {
14864 MOZ_ASSERT(XRE_IsParentProcess());
14865 // If we are in the chrome process, and the window has not been in
14866 // fullscreen, we certainly need to make that fullscreen first.
14867 if (!aRootWin->GetFullScreen()) {
14868 return false;
14869 }
14870 // The iterator not being at end indicates there is still some
14871 // pending fullscreen request relates to this document. We have to
14872 // push the request to the pending queue so requests are handled
14873 // in the correct order.
14874 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
14875 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14876 if (!iter.AtEnd()) {
14877 return false;
14878 }
14879 // We have to apply the fullscreen state directly in this case,
14880 // because nsGlobalWindow::SetFullscreenInternal() will do nothing
14881 // if it is already in fullscreen. If we do not apply the state but
14882 // instead add it to the queue and wait for the window as normal,
14883 // we would get stuck.
14884 return true;
14885 }
14886
CheckFullscreenAllowedElementType(const Element * elem)14887 static bool CheckFullscreenAllowedElementType(const Element* elem) {
14888 // Per spec only HTML, <svg>, and <math> should be allowed, but
14889 // we also need to allow XUL elements right now.
14890 return elem->IsHTMLElement() || elem->IsXULElement() ||
14891 elem->IsSVGElement(nsGkAtoms::svg) ||
14892 elem->IsMathMLElement(nsGkAtoms::math);
14893 }
14894
RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,bool applyFullScreenDirectly)14895 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest,
14896 bool applyFullScreenDirectly) {
14897 if (XRE_IsContentProcess()) {
14898 RequestFullscreenInContentProcess(std::move(aRequest),
14899 applyFullScreenDirectly);
14900 } else {
14901 RequestFullscreenInParentProcess(std::move(aRequest),
14902 applyFullScreenDirectly);
14903 }
14904 }
14905
RequestFullscreenInContentProcess(UniquePtr<FullscreenRequest> aRequest,bool applyFullScreenDirectly)14906 void Document::RequestFullscreenInContentProcess(
14907 UniquePtr<FullscreenRequest> aRequest, bool applyFullScreenDirectly) {
14908 MOZ_ASSERT(XRE_IsContentProcess());
14909
14910 // If we are in the content process, we can apply the fullscreen
14911 // state directly only if we have been in DOM fullscreen, because
14912 // otherwise we always need to notify the chrome.
14913 if (applyFullScreenDirectly ||
14914 !!nsContentUtils::GetInProcessSubtreeRootDocument(this)
14915 ->GetUnretargetedFullScreenElement()) {
14916 ApplyFullscreen(std::move(aRequest));
14917 return;
14918 }
14919
14920 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
14921 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
14922 return;
14923 }
14924
14925 // We don't need to check element ready before this point, because
14926 // if we called ApplyFullscreen, it would check that for us.
14927 if (!FullscreenElementReadyCheck(*aRequest)) {
14928 return;
14929 }
14930
14931 PendingFullscreenChangeList::Add(std::move(aRequest));
14932 // If we are not the top level process, dispatch an event to make
14933 // our parent process go fullscreen first.
14934 nsContentUtils::DispatchEventOnlyToChrome(
14935 this, ToSupports(this), u"MozDOMFullscreen:Request"_ns, CanBubble::eYes,
14936 Cancelable::eNo, /* DefaultAction */ nullptr);
14937 }
14938
RequestFullscreenInParentProcess(UniquePtr<FullscreenRequest> aRequest,bool applyFullScreenDirectly)14939 void Document::RequestFullscreenInParentProcess(
14940 UniquePtr<FullscreenRequest> aRequest, bool applyFullScreenDirectly) {
14941 MOZ_ASSERT(XRE_IsParentProcess());
14942 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
14943 if (!rootWin) {
14944 aRequest->MayRejectPromise("No active window");
14945 return;
14946 }
14947
14948 if (applyFullScreenDirectly || ShouldApplyFullscreenDirectly(this, rootWin)) {
14949 ApplyFullscreen(std::move(aRequest));
14950 return;
14951 }
14952
14953 if (!CheckFullscreenAllowedElementType(aRequest->Element())) {
14954 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML");
14955 return;
14956 }
14957
14958 // We don't need to check element ready before this point, because
14959 // if we called ApplyFullscreen, it would check that for us.
14960 if (!FullscreenElementReadyCheck(*aRequest)) {
14961 return;
14962 }
14963
14964 PendingFullscreenChangeList::Add(std::move(aRequest));
14965 // Make the window fullscreen.
14966 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
14967 }
14968
14969 /* static */
HandlePendingFullscreenRequests(Document * aDoc)14970 bool Document::HandlePendingFullscreenRequests(Document* aDoc) {
14971 bool handled = false;
14972 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
14973 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot);
14974 while (!iter.AtEnd()) {
14975 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
14976 Document* doc = request->Document();
14977 if (doc->ApplyFullscreen(std::move(request))) {
14978 handled = true;
14979 }
14980 }
14981 return handled;
14982 }
14983
ClearPendingFullscreenRequests(Document * aDoc)14984 static void ClearPendingFullscreenRequests(Document* aDoc) {
14985 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter(
14986 aDoc, PendingFullscreenChangeList::eInclusiveDescendants);
14987 while (!iter.AtEnd()) {
14988 UniquePtr<FullscreenRequest> request = iter.TakeAndNext();
14989 request->MayRejectPromise("Fullscreen request aborted");
14990 }
14991 }
14992
ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest)14993 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
14994 if (!FullscreenElementReadyCheck(*aRequest)) {
14995 return false;
14996 }
14997
14998 // Stash a reference to any existing fullscreen doc, we'll use this later
14999 // to detect if the origin which is fullscreen has changed.
15000 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this);
15001
15002 // Stores a list of documents which we must dispatch "fullscreenchange"
15003 // too. We're required by the spec to dispatch the events in root-to-leaf
15004 // order, but we traverse the doctree in a leaf-to-root order, so we save
15005 // references to the documents we must dispatch to so that we get the order
15006 // as specified.
15007 AutoTArray<Document*, 8> changed;
15008
15009 // Remember the root document, so that if a fullscreen document is hidden
15010 // we can reset fullscreen state in the remaining visible fullscreen
15011 // documents.
15012 Document* fullScreenRootDoc =
15013 nsContentUtils::GetInProcessSubtreeRootDocument(this);
15014
15015 // If a document is already in fullscreen, then unlock the mouse pointer
15016 // before setting a new document to fullscreen
15017 PointerLockManager::Unlock();
15018
15019 // Set the fullscreen element. This sets the fullscreen style on the
15020 // element, and the fullscreen-ancestor styles on ancestors of the element
15021 // in this document.
15022 Element* elem = aRequest->Element();
15023 SetFullscreenElement(elem);
15024 // Set the iframe fullscreen flag.
15025 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
15026 iframe->SetFullscreenFlag(true);
15027 }
15028 changed.AppendElement(this);
15029
15030 // Propagate up the document hierarchy, setting the fullscreen element as
15031 // the element's container in ancestor documents. This also sets the
15032 // appropriate css styles as well. Note we don't propagate down the
15033 // document hierarchy, the fullscreen element (or its container) is not
15034 // visible there. Stop when we reach the root document.
15035 Document* child = this;
15036 while (true) {
15037 child->SetFullscreenRoot(fullScreenRootDoc);
15038
15039 // When entering fullscreen, reset the RCD's resolution to the intrinsic
15040 // resolution, otherwise the fullscreen content could be sized larger than
15041 // the screen (since fullscreen is implemented using position:fixed and
15042 // fixed elements are sized to the layout viewport).
15043 // This also ensures that things like video controls aren't zoomed in
15044 // when in fullscreen mode.
15045 if (PresShell* presShell = child->GetPresShell()) {
15046 if (RefPtr<MobileViewportManager> manager =
15047 presShell->GetMobileViewportManager()) {
15048 // Save the previous resolution so it can be restored.
15049 child->mSavedResolution = presShell->GetResolution();
15050 presShell->SetResolutionAndScaleTo(
15051 manager->ComputeIntrinsicResolution(),
15052 ResolutionChangeOrigin::MainThreadRestore);
15053 }
15054 }
15055
15056 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
15057 "Fullscreen root should be set!");
15058 if (child == fullScreenRootDoc) {
15059 break;
15060 }
15061
15062 Element* element = child->GetEmbedderElement();
15063 if (!element) {
15064 // We've reached the root.No more changes need to be made
15065 // to the top layer stacks of documents further up the tree.
15066 break;
15067 }
15068
15069 Document* parent = child->GetInProcessParentDocument();
15070 parent->SetFullscreenElement(element);
15071 changed.AppendElement(parent);
15072 child = parent;
15073 }
15074
15075 FullscreenRoots::Add(this);
15076
15077 // If it is the first entry of the fullscreen, trigger an event so
15078 // that the UI can response to this change, e.g. hide chrome, or
15079 // notifying parent process to enter fullscreen. Note that chrome
15080 // code may also want to listen to MozDOMFullscreen:NewOrigin event
15081 // to pop up warning UI.
15082 if (!previousFullscreenDoc) {
15083 nsContentUtils::DispatchEventOnlyToChrome(
15084 this, ToSupports(elem), u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes,
15085 Cancelable::eNo, /* DefaultAction */ nullptr);
15086 }
15087
15088 // The origin which is fullscreen gets changed. Trigger an event so
15089 // that the chrome knows to pop up a warning UI. Note that
15090 // previousFullscreenDoc == nullptr upon first entry, so we always
15091 // take this path on the first entry. Also note that, in a multi-
15092 // process browser, the code in content process is responsible for
15093 // sending message with the origin to its parent, and the parent
15094 // shouldn't rely on this event itself.
15095 if (aRequest->mShouldNotifyNewOrigin &&
15096 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
15097 DispatchFullscreenNewOriginEvent(this);
15098 }
15099
15100 // Dispatch "fullscreenchange" events. Note that the loop order is
15101 // reversed so that events are dispatched in the tree order as
15102 // indicated in the spec.
15103 for (Document* d : Reversed(changed)) {
15104 DispatchFullscreenChange(*d, d->GetUnretargetedFullScreenElement());
15105 }
15106 aRequest->MayResolvePromise();
15107 return true;
15108 }
15109
ClearOrientationPendingPromise()15110 void Document::ClearOrientationPendingPromise() {
15111 mOrientationPendingPromise = nullptr;
15112 }
15113
SetOrientationPendingPromise(Promise * aPromise)15114 bool Document::SetOrientationPendingPromise(Promise* aPromise) {
15115 if (mIsGoingAway) {
15116 return false;
15117 }
15118
15119 mOrientationPendingPromise = aPromise;
15120 return true;
15121 }
15122
UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent)15123 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) {
15124 dom::VisibilityState oldState = mVisibilityState;
15125 mVisibilityState = ComputeVisibilityState();
15126 if (oldState != mVisibilityState) {
15127 if (aDispatchEvent == DispatchVisibilityChange::Yes) {
15128 nsContentUtils::DispatchTrustedEvent(this, ToSupports(this),
15129 u"visibilitychange"_ns,
15130 CanBubble::eYes, Cancelable::eNo);
15131 }
15132 NotifyActivityChanged();
15133 if (mVisibilityState == dom::VisibilityState::Visible) {
15134 MaybeActiveMediaComponents();
15135 }
15136
15137 bool visible = !Hidden();
15138 for (auto* listener : mWorkerListeners) {
15139 listener->OnVisible(visible);
15140 }
15141 }
15142 }
15143
AddWorkerDocumentListener(WorkerDocumentListener * aListener)15144 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) {
15145 mWorkerListeners.Insert(aListener);
15146 aListener->OnVisible(!Hidden());
15147 }
15148
RemoveWorkerDocumentListener(WorkerDocumentListener * aListener)15149 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) {
15150 mWorkerListeners.Remove(aListener);
15151 }
15152
ComputeVisibilityState() const15153 VisibilityState Document::ComputeVisibilityState() const {
15154 // We have to check a few pieces of information here:
15155 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters.
15156 // 2) Do we have an outer window? If not, we're hidden. Note that we don't
15157 // want to use GetWindow here because it does weird groveling for windows
15158 // in some cases.
15159 // 3) Is our outer window background? If so, we're hidden.
15160 // Otherwise, we're visible.
15161 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
15162 mWindow->GetOuterWindow()->IsBackground()) {
15163 return dom::VisibilityState::Hidden;
15164 }
15165
15166 return dom::VisibilityState::Visible;
15167 }
15168
PostVisibilityUpdateEvent()15169 void Document::PostVisibilityUpdateEvent() {
15170 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>(
15171 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState,
15172 DispatchVisibilityChange::Yes);
15173 Dispatch(TaskCategory::Other, event.forget());
15174 }
15175
MaybeActiveMediaComponents()15176 void Document::MaybeActiveMediaComponents() {
15177 auto* window = GetWindow();
15178 if (!window || !window->ShouldDelayMediaFromStart()) {
15179 return;
15180 }
15181 window->ActivateMediaComponents();
15182 }
15183
DocAddSizeOfExcludingThis(nsWindowSizes & aWindowSizes) const15184 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const {
15185 nsINode::AddSizeOfExcludingThis(aWindowSizes,
15186 &aWindowSizes.mDOMSizes.mDOMOtherSize);
15187
15188 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) {
15189 AddSizeOfNodeTree(*kid, aWindowSizes);
15190 }
15191
15192 // IMPORTANT: for our ComputedValues measurements, we want to measure
15193 // ComputedValues accessible from DOM elements before ComputedValues not
15194 // accessible from DOM elements (i.e. accessible only from the frame tree).
15195 //
15196 // Therefore, the measurement of the Document superclass must happen after
15197 // the measurement of DOM nodes (above), because Document contains the
15198 // PresShell, which contains the frame tree.
15199 if (mPresShell) {
15200 mPresShell->AddSizeOfIncludingThis(aWindowSizes);
15201 }
15202
15203 mStyleSet->AddSizeOfIncludingThis(aWindowSizes);
15204
15205 aWindowSizes.mPropertyTablesSize +=
15206 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15207
15208 if (EventListenerManager* elm = GetExistingListenerManager()) {
15209 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15210 }
15211
15212 if (mNodeInfoManager) {
15213 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes);
15214 }
15215
15216 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15217 mDOMMediaQueryLists.sizeOfExcludingThis(
15218 aWindowSizes.mState.mMallocSizeOf);
15219
15220 for (const MediaQueryList* mql : mDOMMediaQueryLists) {
15221 aWindowSizes.mDOMSizes.mDOMMediaQueryLists +=
15222 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf);
15223 }
15224
15225 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes);
15226
15227 for (auto& sheetArray : mAdditionalSheets) {
15228 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray);
15229 }
15230 // Lumping in the loader with the style-sheets size is not ideal,
15231 // but most of the things in there are in fact stylesheets, so it
15232 // doesn't seem worthwhile to separate it out.
15233 // This can be null if we've already been unlinked.
15234 if (mCSSLoader) {
15235 aWindowSizes.mLayoutStyleSheetsSize +=
15236 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
15237 }
15238
15239 if (mResizeObserverController) {
15240 mResizeObserverController->AddSizeOfIncludingThis(aWindowSizes);
15241 }
15242
15243 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15244 mAttrStyleSheet ? mAttrStyleSheet->DOMSizeOfIncludingThis(
15245 aWindowSizes.mState.mMallocSizeOf)
15246 : 0;
15247
15248 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15249 mStyledLinks.ShallowSizeOfExcludingThis(
15250 aWindowSizes.mState.mMallocSizeOf);
15251
15252 // Measurement of the following members may be added later if DMD finds it
15253 // is worthwhile:
15254 // - mMidasCommandManager
15255 // - many!
15256 }
15257
DocAddSizeOfIncludingThis(nsWindowSizes & aWindowSizes) const15258 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const {
15259 aWindowSizes.mDOMSizes.mDOMOtherSize +=
15260 aWindowSizes.mState.mMallocSizeOf(this);
15261 DocAddSizeOfExcludingThis(aWindowSizes);
15262 }
15263
AddSizeOfExcludingThis(nsWindowSizes & aSizes,size_t * aNodeSize) const15264 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
15265 size_t* aNodeSize) const {
15266 // This AddSizeOfExcludingThis() overrides the one from nsINode. But
15267 // nsDocuments can only appear at the top of the DOM tree, and we use the
15268 // specialized DocAddSizeOfExcludingThis() in that case. So this should never
15269 // be called.
15270 MOZ_CRASH();
15271 }
15272
15273 /* static */
AddSizeOfNodeTree(nsINode & aNode,nsWindowSizes & aWindowSizes)15274 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) {
15275 size_t nodeSize = 0;
15276 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize);
15277
15278 // This is where we transfer the nodeSize obtained from
15279 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes.
15280 switch (aNode.NodeType()) {
15281 case nsINode::ELEMENT_NODE:
15282 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize;
15283 break;
15284 case nsINode::TEXT_NODE:
15285 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize;
15286 break;
15287 case nsINode::CDATA_SECTION_NODE:
15288 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize;
15289 break;
15290 case nsINode::COMMENT_NODE:
15291 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize;
15292 break;
15293 default:
15294 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize;
15295 break;
15296 }
15297
15298 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) {
15299 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
15300 }
15301
15302 if (aNode.IsContent()) {
15303 nsTArray<nsIContent*> anonKids;
15304 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids,
15305 nsIContent::eAllChildren);
15306 for (nsIContent* anonKid : anonKids) {
15307 AddSizeOfNodeTree(*anonKid, aWindowSizes);
15308 }
15309
15310 if (auto* element = Element::FromNode(aNode)) {
15311 if (ShadowRoot* shadow = element->GetShadowRoot()) {
15312 AddSizeOfNodeTree(*shadow, aWindowSizes);
15313 }
15314 }
15315 }
15316
15317 // NOTE(emilio): If you feel smart and want to change this function to use
15318 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a
15319 // sane way, and kids of <content> won't point to the parent, so we'd never
15320 // find the root node where we should stop at.
15321 for (nsIContent* kid = aNode.GetFirstChild(); kid;
15322 kid = kid->GetNextSibling()) {
15323 AddSizeOfNodeTree(*kid, aWindowSizes);
15324 }
15325 }
15326
Constructor(const GlobalObject & aGlobal,ErrorResult & rv)15327 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal,
15328 ErrorResult& rv) {
15329 nsCOMPtr<nsIScriptGlobalObject> global =
15330 do_QueryInterface(aGlobal.GetAsSupports());
15331 if (!global) {
15332 rv.Throw(NS_ERROR_UNEXPECTED);
15333 return nullptr;
15334 }
15335
15336 nsCOMPtr<nsIScriptObjectPrincipal> prin =
15337 do_QueryInterface(aGlobal.GetAsSupports());
15338 if (!prin) {
15339 rv.Throw(NS_ERROR_UNEXPECTED);
15340 return nullptr;
15341 }
15342
15343 nsCOMPtr<nsIURI> uri;
15344 NS_NewURI(getter_AddRefs(uri), "about:blank");
15345 if (!uri) {
15346 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
15347 return nullptr;
15348 }
15349
15350 nsCOMPtr<Document> doc;
15351 nsresult res = NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns,
15352 nullptr, uri, uri, prin->GetPrincipal(),
15353 true, global, DocumentFlavorPlain);
15354 if (NS_FAILED(res)) {
15355 rv.Throw(res);
15356 return nullptr;
15357 }
15358
15359 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
15360
15361 return doc.forget();
15362 }
15363
CreateExpression(const nsAString & aExpression,XPathNSResolver * aResolver,ErrorResult & rv)15364 XPathExpression* Document::CreateExpression(const nsAString& aExpression,
15365 XPathNSResolver* aResolver,
15366 ErrorResult& rv) {
15367 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
15368 }
15369
CreateNSResolver(nsINode & aNodeResolver)15370 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) {
15371 return XPathEvaluator()->CreateNSResolver(aNodeResolver);
15372 }
15373
Evaluate(JSContext * aCx,const nsAString & aExpression,nsINode & aContextNode,XPathNSResolver * aResolver,uint16_t aType,JS::Handle<JSObject * > aResult,ErrorResult & rv)15374 already_AddRefed<XPathResult> Document::Evaluate(
15375 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
15376 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
15377 ErrorResult& rv) {
15378 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver,
15379 aType, aResult, rv);
15380 }
15381
GetAppWindowIfToplevelChrome() const15382 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const {
15383 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
15384 if (!item) {
15385 return nullptr;
15386 }
15387 nsCOMPtr<nsIDocShellTreeOwner> owner;
15388 item->GetTreeOwner(getter_AddRefs(owner));
15389 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner);
15390 if (!appWin) {
15391 return nullptr;
15392 }
15393 nsCOMPtr<nsIDocShell> appWinShell;
15394 appWin->GetDocShell(getter_AddRefs(appWinShell));
15395 if (!SameCOMIdentity(appWinShell, item)) {
15396 return nullptr;
15397 }
15398 return appWin.forget();
15399 }
15400
GetTopLevelWindowContext() const15401 WindowContext* Document::GetTopLevelWindowContext() const {
15402 WindowContext* windowContext = GetWindowContext();
15403 return windowContext ? windowContext->TopWindowContext() : nullptr;
15404 }
15405
GetTopLevelContentDocumentIfSameProcess()15406 Document* Document::GetTopLevelContentDocumentIfSameProcess() {
15407 Document* parent;
15408
15409 if (!mLoadedAsData) {
15410 parent = this;
15411 } else {
15412 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15413 if (!window) {
15414 return nullptr;
15415 }
15416
15417 parent = window->GetExtantDoc();
15418 if (!parent) {
15419 return nullptr;
15420 }
15421 }
15422
15423 do {
15424 if (parent->IsTopLevelContentDocument()) {
15425 break;
15426 }
15427
15428 // If we ever have a non-content parent before we hit a toplevel content
15429 // parent, then we're never going to find one. Just bail.
15430 if (!parent->IsContentDocument()) {
15431 return nullptr;
15432 }
15433
15434 parent = parent->GetInProcessParentDocument();
15435 } while (parent);
15436
15437 return parent;
15438 }
15439
GetTopLevelContentDocumentIfSameProcess() const15440 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const {
15441 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess();
15442 }
15443
PropagateImageUseCounters(Document * aReferencingDocument)15444 void Document::PropagateImageUseCounters(Document* aReferencingDocument) {
15445 MOZ_ASSERT(IsBeingUsedAsImage());
15446 MOZ_ASSERT(aReferencingDocument);
15447
15448 if (!aReferencingDocument->mShouldReportUseCounters) {
15449 // No need to propagate use counters to a document that itself won't report
15450 // use counters.
15451 return;
15452 }
15453
15454 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15455 ("PropagateImageUseCounters from %s to %s",
15456 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(),
15457 nsContentUtils::TruncatedURLForDisplay(
15458 aReferencingDocument->mDocumentURI)
15459 .get()));
15460
15461 if (aReferencingDocument->IsBeingUsedAsImage()) {
15462 NS_WARNING(
15463 "Page use counters from nested image documents may not "
15464 "propagate to the top-level document (bug 1657805)");
15465 }
15466
15467 SetCssUseCounterBits();
15468 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters;
15469 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters;
15470 }
15471
HasScriptsBlockedBySandbox()15472 bool Document::HasScriptsBlockedBySandbox() {
15473 return mSandboxFlags & SANDBOXED_SCRIPTS;
15474 }
15475
15476 // Some use-counter sanity-checking.
15477 static_assert(size_t(eUseCounter_EndCSSProperties) -
15478 size_t(eUseCounter_FirstCSSProperty) ==
15479 size_t(eCSSProperty_COUNT_with_aliases),
15480 "We should have the right amount of CSS property use counters");
15481 static_assert(size_t(eUseCounter_Count) -
15482 size_t(eUseCounter_FirstCountedUnknownProperty) ==
15483 size_t(CountedUnknownProperty::Count),
15484 "We should have the right amount of counted unknown properties"
15485 " use counters");
15486 static_assert(size_t(eUseCounter_Count) * 2 ==
15487 size_t(Telemetry::HistogramUseCounterCount),
15488 "There should be two histograms (document and page)"
15489 " for each use counter");
15490
15491 #define ASSERT_CSS_COUNTER(id_, method_) \
15492 static_assert(size_t(eUseCounter_property_##method_) - \
15493 size_t(eUseCounter_FirstCSSProperty) == \
15494 size_t(id_), \
15495 "Order for CSS counters and CSS property id should match");
15496 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
15497 #define CSS_PROP_LONGHAND(name_, id_, method_, ...) \
15498 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
15499 #define CSS_PROP_SHORTHAND(name_, id_, method_, ...) \
15500 ASSERT_CSS_COUNTER(eCSSProperty_##id_, method_)
15501 #define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, ...) \
15502 ASSERT_CSS_COUNTER(eCSSPropertyAlias_##aliasid_, method_)
15503 #include "mozilla/ServoCSSPropList.h"
15504 #undef CSS_PROP_ALIAS
15505 #undef CSS_PROP_SHORTHAND
15506 #undef CSS_PROP_LONGHAND
15507 #undef CSS_PROP_PUBLIC_OR_PRIVATE
15508 #undef ASSERT_CSS_COUNTER
15509
SetCssUseCounterBits()15510 void Document::SetCssUseCounterBits() {
15511 if (StaticPrefs::layout_css_use_counters_enabled()) {
15512 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) {
15513 auto id = nsCSSPropertyID(i);
15514 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) {
15515 SetUseCounter(nsCSSProps::UseCounterFor(id));
15516 }
15517 }
15518 }
15519
15520 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) {
15521 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) {
15522 auto id = CountedUnknownProperty(i);
15523 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(),
15524 id)) {
15525 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i));
15526 }
15527 }
15528 }
15529 }
15530
InitUseCounters()15531 void Document::InitUseCounters() {
15532 // We can be called more than once, e.g. when session history navigation shows
15533 // us a second time.
15534 if (mUseCountersInitialized) {
15535 return;
15536 }
15537 mUseCountersInitialized = true;
15538
15539 static_assert(Telemetry::HistogramUseCounterCount > 0);
15540
15541 if (!ShouldIncludeInTelemetry(/* aAllowExtensionURIs = */ true)) {
15542 return;
15543 }
15544
15545 // Now we know for sure that we should report use counters from this document.
15546 mShouldReportUseCounters = true;
15547
15548 WindowContext* top = GetWindowContextForPageUseCounters();
15549 if (!top) {
15550 // This is the case for SVG image documents. They are not displayed in a
15551 // window, but we still do want to record document use counters for them.
15552 //
15553 // Page use counter propagation is handled in PropagateImageUseCounters,
15554 // so there is no need to use the cross-process machinery to send them.
15555 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15556 ("InitUseCounters for a non-displayed document [%s]",
15557 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
15558 return;
15559 }
15560
15561 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
15562 if (!wgc) {
15563 return;
15564 }
15565
15566 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15567 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64
15568 " [from %s]",
15569 wgc->InnerWindowId(), top->Id(),
15570 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get()));
15571
15572 // Inform the parent process that we will send it page use counters later on.
15573 wgc->SendExpectPageUseCounters(top);
15574 mShouldSendPageUseCounters = true;
15575 }
15576
15577 // We keep separate counts for individual documents and top-level
15578 // pages to more accurately track how many web pages might break if
15579 // certain features were removed. Consider the case of a single
15580 // HTML document with several SVG images and/or iframes with
15581 // sub-documents of their own. If we maintained a single set of use
15582 // counters and all the sub-documents use a particular feature, then
15583 // telemetry would indicate that we would be breaking N documents if
15584 // that feature were removed. Whereas with a document/top-level
15585 // page split, we can see that N documents would be affected, but
15586 // only a single web page would be affected.
15587 //
15588 // The difference between the values of these two histograms and the
15589 // related use counters below tell us how many pages did *not* use
15590 // the feature in question. For instance, if we see that a given
15591 // session has destroyed 30 content documents, but a particular use
15592 // counter shows only a count of 5, we can infer that the use
15593 // counter was *not* used in 25 of those 30 documents.
15594 //
15595 // We do things this way, rather than accumulating a boolean flag
15596 // for each use counter, to avoid sending histograms for features
15597 // that don't get widely used. Doing things in this fashion means
15598 // smaller telemetry payloads and faster processing on the server
15599 // side.
ReportDocumentUseCounters()15600 void Document::ReportDocumentUseCounters() {
15601 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) {
15602 return;
15603 }
15604
15605 mReportedDocumentUseCounters = true;
15606
15607 // Note that a document is being destroyed. See the comment above for how
15608 // use counter histograms are interpreted relative to this measurement.
15609 // TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED is recorded in
15610 // WindowGlobalParent::FinishAccumulatingPageUseCounters.
15611 Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
15612
15613 // Ask all of our resource documents to report their own document use
15614 // counters.
15615 EnumerateExternalResources([](Document& aDoc) {
15616 aDoc.ReportDocumentUseCounters();
15617 return CallState::Continue;
15618 });
15619
15620 // Copy StyleUseCounters into our document use counters.
15621 SetCssUseCounterBits();
15622
15623 // Report our per-document use counters.
15624 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15625 ("Reporting document use counters [%s]",
15626 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
15627 for (int32_t c = 0; c < eUseCounter_Count; ++c) {
15628 auto uc = static_cast<UseCounter>(c);
15629 if (!mUseCounters[uc]) {
15630 continue;
15631 }
15632
15633 auto id = static_cast<Telemetry::HistogramID>(
15634 Telemetry::HistogramFirstUseCounter + uc * 2);
15635 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15636 (" > %s\n", Telemetry::GetHistogramName(id)));
15637 Telemetry::Accumulate(id, 1);
15638 }
15639
15640 ReportDocumentLazyLoadCounters();
15641 }
15642
ReportDocumentLazyLoadCounters()15643 void Document::ReportDocumentLazyLoadCounters() {
15644 if (!mLazyLoadImageCount) {
15645 return;
15646 }
15647 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_TOTAL, mLazyLoadImageCount);
15648 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_STARTED,
15649 mLazyLoadImageStarted);
15650 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_NOT_VIEWPORT,
15651 mLazyLoadImageStarted -
15652 mLazyLoadImageReachViewportLoading -
15653 mLazyLoadImageReachViewportLoaded);
15654 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_VIEWPORT_LOADING,
15655 mLazyLoadImageReachViewportLoading);
15656 Telemetry::Accumulate(Telemetry::LAZYLOAD_IMAGE_VIEWPORT_LOADED,
15657 mLazyLoadImageReachViewportLoaded);
15658 }
15659
SendPageUseCounters()15660 void Document::SendPageUseCounters() {
15661 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) {
15662 return;
15663 }
15664
15665 // Ask all of our resource documents to send their own document use
15666 // counters to the parent process to be counted as page use counters.
15667 EnumerateExternalResources([](Document& aDoc) {
15668 aDoc.SendPageUseCounters();
15669 return CallState::Continue;
15670 });
15671
15672 // Send our use counters to the parent process to accumulate them towards the
15673 // page use counters for the top-level document.
15674 //
15675 // We take our own document use counters (those in mUseCounters) and any child
15676 // document use counters (those in mChildDocumentUseCounters) that have been
15677 // explicitly propagated up to us, which includes resource documents, static
15678 // clones, and SVG images.
15679 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild();
15680 if (!wgc) {
15681 MOZ_ASSERT_UNREACHABLE(
15682 "SendPageUseCounters should be called while we still have access "
15683 "to our WindowContext");
15684 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15685 (" > too late to send page use counters"));
15686 return;
15687 }
15688
15689 MOZ_LOG(gUseCountersLog, LogLevel::Debug,
15690 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]",
15691 wgc->WindowContext()->Id(),
15692 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get()));
15693
15694 // Copy StyleUseCounters into our document use counters.
15695 SetCssUseCounterBits();
15696
15697 UseCounters counters = mUseCounters | mChildDocumentUseCounters;
15698 wgc->SendAccumulatePageUseCounters(counters);
15699 }
15700
GetWindowContextForPageUseCounters() const15701 WindowContext* Document::GetWindowContextForPageUseCounters() const {
15702 if (mDisplayDocument) {
15703 // If we are a resource document, then go through it to find the
15704 // top-level document.
15705 return mDisplayDocument->GetWindowContextForPageUseCounters();
15706 }
15707
15708 if (mOriginalDocument) {
15709 // For static clones (print preview documents), contribute page use counters
15710 // towards the original document.
15711 return mOriginalDocument->GetWindowContextForPageUseCounters();
15712 }
15713
15714 WindowContext* wc = GetTopLevelWindowContext();
15715 if (!wc || !wc->GetBrowsingContext()->IsContent()) {
15716 return nullptr;
15717 }
15718
15719 return wc;
15720 }
15721
UpdateIntersectionObservations(TimeStamp aNowTime)15722 void Document::UpdateIntersectionObservations(TimeStamp aNowTime) {
15723 if (mIntersectionObservers.IsEmpty()) {
15724 return;
15725 }
15726
15727 DOMHighResTimeStamp time = 0;
15728 if (nsPIDOMWindowInner* win = GetInnerWindow()) {
15729 if (Performance* perf = win->GetPerformance()) {
15730 time = perf->TimeStampToDOMHighResForRendering(aNowTime);
15731 }
15732 }
15733
15734 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
15735 mIntersectionObservers);
15736 for (const auto& observer : observers) {
15737 if (observer) {
15738 observer->Update(this, time);
15739 }
15740 }
15741 }
15742
ScheduleIntersectionObserverNotification()15743 void Document::ScheduleIntersectionObserverNotification() {
15744 if (mIntersectionObservers.IsEmpty()) {
15745 return;
15746 }
15747 MOZ_RELEASE_ASSERT(NS_IsMainThread());
15748 nsCOMPtr<nsIRunnable> notification =
15749 NewRunnableMethod("Document::NotifyIntersectionObservers", this,
15750 &Document::NotifyIntersectionObservers);
15751 Dispatch(TaskCategory::Other, notification.forget());
15752 }
15753
NotifyIntersectionObservers()15754 void Document::NotifyIntersectionObservers() {
15755 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>(
15756 mIntersectionObservers);
15757 for (const auto& observer : observers) {
15758 if (observer) {
15759 // MOZ_KnownLive because the 'observers' array guarantees to keep it
15760 // alive.
15761 MOZ_KnownLive(observer)->Notify();
15762 }
15763 }
15764 }
15765
EnsureLazyLoadImageObserver()15766 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserver() {
15767 if (!mLazyLoadImageObserver) {
15768 mLazyLoadImageObserver =
15769 DOMIntersectionObserver::CreateLazyLoadObserver(*this);
15770 }
15771 return *mLazyLoadImageObserver;
15772 }
15773
EnsureLazyLoadImageObserverViewport()15774 DOMIntersectionObserver& Document::EnsureLazyLoadImageObserverViewport() {
15775 if (!mLazyLoadImageObserverViewport) {
15776 mLazyLoadImageObserverViewport =
15777 DOMIntersectionObserver::CreateLazyLoadObserverViewport(*this);
15778 }
15779 return *mLazyLoadImageObserverViewport;
15780 }
15781
IncLazyLoadImageReachViewport(bool aLoading)15782 void Document::IncLazyLoadImageReachViewport(bool aLoading) {
15783 if (aLoading) {
15784 ++mLazyLoadImageReachViewportLoading;
15785 } else {
15786 ++mLazyLoadImageReachViewportLoaded;
15787 }
15788 }
15789
NotifyLayerManagerRecreated()15790 void Document::NotifyLayerManagerRecreated() {
15791 NotifyActivityChanged();
15792 EnumerateSubDocuments([](Document& aSubDoc) {
15793 aSubDoc.NotifyLayerManagerRecreated();
15794 return CallState::Continue;
15795 });
15796 }
15797
XPathEvaluator()15798 XPathEvaluator* Document::XPathEvaluator() {
15799 if (!mXPathEvaluator) {
15800 mXPathEvaluator.reset(new dom::XPathEvaluator(this));
15801 }
15802 return mXPathEvaluator.get();
15803 }
15804
GetCachedEncoder()15805 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() {
15806 return mCachedEncoder.forget();
15807 }
15808
SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder)15809 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) {
15810 mCachedEncoder = aEncoder;
15811 }
15812
GetLoadContext() const15813 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; }
15814
GetDocShell() const15815 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; }
15816
SetStateObject(nsIStructuredCloneContainer * scContainer)15817 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) {
15818 mStateObjectContainer = scContainer;
15819 mStateObjectCached = nullptr;
15820 }
15821
GetDocumentLWTheme() const15822 Document::DocumentTheme Document::GetDocumentLWTheme() const {
15823 if (!NodePrincipal()->IsSystemPrincipal()) {
15824 return DocumentTheme::None;
15825 }
15826
15827 auto theme = DocumentTheme::None; // No lightweight theme by default
15828 Element* element = GetRootElement();
15829 if (element && element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::lwtheme,
15830 nsGkAtoms::_true, eCaseMatters)) {
15831 theme = DocumentTheme::Neutral;
15832 nsAutoString lwTheme;
15833 element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
15834 if (lwTheme.EqualsLiteral("dark")) {
15835 theme = DocumentTheme::Dark;
15836 } else if (lwTheme.EqualsLiteral("bright")) {
15837 theme = DocumentTheme::Bright;
15838 }
15839 }
15840
15841 return theme;
15842 }
15843
CreateHTMLElement(nsAtom * aTag)15844 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) {
15845 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
15846 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
15847 ELEMENT_NODE);
15848 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail");
15849
15850 nsCOMPtr<Element> element;
15851 DebugOnly<nsresult> rv =
15852 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(),
15853 mozilla::dom::NOT_FROM_PARSER);
15854
15855 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
15856 return element.forget();
15857 }
15858
SuppressBrowsingContext(BrowsingContext * aContext)15859 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext(
15860 BrowsingContext* aContext) {
15861 aContext->PreOrderWalk([&](BrowsingContext* aBC) {
15862 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
15863 if (RefPtr<Document> doc = win->GetExtantDoc()) {
15864 SuppressDocument(doc);
15865 mDocuments.AppendElement(doc);
15866 }
15867 }
15868 });
15869 }
15870
SuppressBrowsingContextGroup(BrowsingContextGroup * aGroup)15871 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup(
15872 BrowsingContextGroup* aGroup) {
15873 for (const auto& bc : aGroup->Toplevels()) {
15874 SuppressBrowsingContext(bc);
15875 }
15876 }
15877
nsAutoSyncOperation(Document * aDoc,SyncOperationBehavior aSyncBehavior)15878 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc,
15879 SyncOperationBehavior aSyncBehavior)
15880 : mSyncBehavior(aSyncBehavior) {
15881 mMicroTaskLevel = 0;
15882 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
15883 mMicroTaskLevel = ccjs->MicroTaskLevel();
15884 ccjs->SetMicroTaskLevel(0);
15885 }
15886 if (aDoc) {
15887 mBrowsingContext = aDoc->GetBrowsingContext();
15888 if (InputTaskManager::CanSuspendInputEvent()) {
15889 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) {
15890 SuppressBrowsingContextGroup(bcg);
15891 }
15892 } else if (mBrowsingContext) {
15893 SuppressBrowsingContext(mBrowsingContext->Top());
15894 }
15895 if (mBrowsingContext &&
15896 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
15897 InputTaskManager::CanSuspendInputEvent()) {
15898 mBrowsingContext->Group()->IncInputEventSuspensionLevel();
15899 }
15900 }
15901 }
15902
SuppressDocument(Document * aDoc)15903 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) {
15904 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
15905 win->TimeoutManager().BeginSyncOperation();
15906 }
15907 aDoc->SetIsInSyncOperation(true);
15908 }
15909
UnsuppressDocument(Document * aDoc)15910 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) {
15911 if (nsCOMPtr<nsPIDOMWindowInner> win = aDoc->GetInnerWindow()) {
15912 win->TimeoutManager().EndSyncOperation();
15913 }
15914 aDoc->SetIsInSyncOperation(false);
15915 }
15916
~nsAutoSyncOperation()15917 nsAutoSyncOperation::~nsAutoSyncOperation() {
15918 UnsuppressDocuments();
15919 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
15920 if (ccjs) {
15921 ccjs->SetMicroTaskLevel(mMicroTaskLevel);
15922 }
15923 if (mBrowsingContext &&
15924 mSyncBehavior == SyncOperationBehavior::eSuspendInput &&
15925 InputTaskManager::CanSuspendInputEvent()) {
15926 mBrowsingContext->Group()->DecInputEventSuspensionLevel();
15927 }
15928 }
15929
SetIsInSyncOperation(bool aSync)15930 void Document::SetIsInSyncOperation(bool aSync) {
15931 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) {
15932 ccjs->UpdateMicroTaskSuppressionGeneration();
15933 }
15934
15935 if (aSync) {
15936 ++mInSyncOperationCount;
15937 } else {
15938 --mInSyncOperationCount;
15939 }
15940 }
15941
GetUserFontSet()15942 gfxUserFontSet* Document::GetUserFontSet() {
15943 if (!mFontFaceSet) {
15944 return nullptr;
15945 }
15946
15947 return mFontFaceSet->GetUserFontSet();
15948 }
15949
FlushUserFontSet()15950 void Document::FlushUserFontSet() {
15951 if (!mFontFaceSetDirty) {
15952 return;
15953 }
15954
15955 mFontFaceSetDirty = false;
15956
15957 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
15958 nsTArray<nsFontFaceRuleContainer> rules;
15959 RefPtr<PresShell> presShell = GetPresShell();
15960 if (presShell) {
15961 MOZ_ASSERT(mStyleSetFilled);
15962 mStyleSet->AppendFontFaceRules(rules);
15963 }
15964
15965 if (!mFontFaceSet && !rules.IsEmpty()) {
15966 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
15967 mFontFaceSet = new FontFaceSet(window, this);
15968 }
15969
15970 bool changed = false;
15971 if (mFontFaceSet) {
15972 changed = mFontFaceSet->UpdateRules(rules);
15973 }
15974
15975 // We need to enqueue a style change reflow (for later) to
15976 // reflect that we're modifying @font-face rules. (However,
15977 // without a reflow, nothing will happen to start any downloads
15978 // that are needed.)
15979 if (changed && presShell) {
15980 if (nsPresContext* presContext = presShell->GetPresContext()) {
15981 presContext->UserFontSetUpdated();
15982 }
15983 }
15984 }
15985 }
15986
MarkUserFontSetDirty()15987 void Document::MarkUserFontSetDirty() {
15988 if (mFontFaceSetDirty) {
15989 return;
15990 }
15991 mFontFaceSetDirty = true;
15992 if (PresShell* presShell = GetPresShell()) {
15993 presShell->EnsureStyleFlush();
15994 }
15995 }
15996
Fonts()15997 FontFaceSet* Document::Fonts() {
15998 if (!mFontFaceSet) {
15999 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
16000 mFontFaceSet = new FontFaceSet(window, this);
16001 FlushUserFontSet();
16002 }
16003 return mFontFaceSet;
16004 }
16005
ReportHasScrollLinkedEffect()16006 void Document::ReportHasScrollLinkedEffect() {
16007 if (mHasScrollLinkedEffect) {
16008 // We already did this once for this document, don't do it again.
16009 return;
16010 }
16011 mHasScrollLinkedEffect = true;
16012 nsContentUtils::ReportToConsole(
16013 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this,
16014 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3");
16015 }
16016
SetSHEntryHasUserInteraction(bool aHasInteraction)16017 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
16018 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16019 // Setting has user interction on a discarded browsing context has
16020 // no effect.
16021 Unused << topWc->SetSHEntryHasUserInteraction(aHasInteraction);
16022 }
16023 }
16024
GetSHEntryHasUserInteraction()16025 bool Document::GetSHEntryHasUserInteraction() {
16026 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) {
16027 return topWc->GetSHEntryHasUserInteraction();
16028 }
16029 return false;
16030 }
16031
SetUserHasInteracted()16032 void Document::SetUserHasInteracted() {
16033 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
16034 ("Document %p has been interacted by user.", this));
16035
16036 // We maybe need to update the user-interaction permission.
16037 MaybeStoreUserInteractionAsPermission();
16038
16039 // For purposes of reducing irrelevant session history entries on
16040 // the back button, we annotate entries with whether they had user
16041 // interaction. This is gated on its own flag on the WindowContext
16042 // (instead of mUserHasInteracted) to account for the fact that multiple
16043 // top-level SH entries can be associated with the same document.
16044 // Thus, whenever we create a new SH entry for this document,
16045 // this flag is reset.
16046 if (!GetSHEntryHasUserInteraction()) {
16047 nsIDocShell* docShell = this->GetDocShell();
16048 if (docShell) {
16049 nsCOMPtr<nsISHEntry> currentEntry;
16050 bool oshe;
16051 nsresult rv =
16052 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
16053 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
16054 currentEntry->SetHasUserInteraction(true);
16055 }
16056 }
16057 SetSHEntryHasUserInteraction(true);
16058 }
16059
16060 if (mUserHasInteracted) {
16061 return;
16062 }
16063
16064 mUserHasInteracted = true;
16065
16066 if (mChannel) {
16067 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
16068 loadInfo->SetDocumentHasUserInteracted(true);
16069 }
16070 // Tell the parent process about user interaction
16071 if (auto* wgc = GetWindowGlobalChild()) {
16072 wgc->SendUpdateDocumentHasUserInteracted(true);
16073 }
16074
16075 MaybeAllowStorageForOpenerAfterUserInteraction();
16076 }
16077
GetBrowsingContext() const16078 BrowsingContext* Document::GetBrowsingContext() const {
16079 nsCOMPtr<nsIDocShell> docshell(mDocumentContainer);
16080 return docshell ? docshell->GetBrowsingContext() : nullptr;
16081 }
16082
NotifyUserGestureActivation()16083 void Document::NotifyUserGestureActivation() {
16084 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16085 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16086 WindowContext* windowContext = aBC->GetCurrentWindowContext();
16087 if (!windowContext) {
16088 return;
16089 }
16090
16091 nsIDocShell* docShell = aBC->GetDocShell();
16092 if (!docShell) {
16093 return;
16094 }
16095
16096 Document* document = docShell->GetDocument();
16097 if (!document) {
16098 return;
16099 }
16100
16101 // XXXedgar we probably could just check `IsInProcess()` after fission
16102 // enable.
16103 if (NodePrincipal()->Equals(document->NodePrincipal())) {
16104 windowContext->NotifyUserGestureActivation();
16105 }
16106 });
16107
16108 for (bc = bc->GetParent(); bc; bc = bc->GetParent()) {
16109 if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
16110 windowContext->NotifyUserGestureActivation();
16111 }
16112 }
16113 }
16114 }
16115
HasBeenUserGestureActivated()16116 bool Document::HasBeenUserGestureActivated() {
16117 RefPtr<WindowContext> wc = GetWindowContext();
16118 return wc && wc->HasBeenUserGestureActivated();
16119 }
16120
ClearUserGestureActivation()16121 void Document::ClearUserGestureActivation() {
16122 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) {
16123 bc = bc->Top();
16124 bc->PreOrderWalk([&](BrowsingContext* aBC) {
16125 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) {
16126 windowContext->NotifyResetUserGestureActivation();
16127 }
16128 });
16129 }
16130 }
16131
HasValidTransientUserGestureActivation() const16132 bool Document::HasValidTransientUserGestureActivation() const {
16133 RefPtr<WindowContext> wc = GetWindowContext();
16134 return wc && wc->HasValidTransientUserGestureActivation();
16135 }
16136
ConsumeTransientUserGestureActivation()16137 bool Document::ConsumeTransientUserGestureActivation() {
16138 RefPtr<WindowContext> wc = GetWindowContext();
16139 return wc && wc->ConsumeTransientUserGestureActivation();
16140 }
16141
IncLazyLoadImageCount()16142 void Document::IncLazyLoadImageCount() {
16143 if (!mLazyLoadImageCount) {
16144 if (WindowContext* wc = GetTopLevelWindowContext()) {
16145 if (!wc->HadLazyLoadImage()) {
16146 Unused << wc->SetHadLazyLoadImage(true);
16147 }
16148 }
16149 }
16150 ++mLazyLoadImageCount;
16151 }
16152
SetDocTreeHadMedia()16153 void Document::SetDocTreeHadMedia() {
16154 RefPtr<WindowContext> topWc = GetTopLevelWindowContext();
16155 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) {
16156 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true));
16157 }
16158 }
16159
AutoplayPolicy() const16160 DocumentAutoplayPolicy Document::AutoplayPolicy() const {
16161 return AutoplayPolicy::IsAllowedToPlay(*this);
16162 }
16163
MaybeAllowStorageForOpenerAfterUserInteraction()16164 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() {
16165 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16166 return;
16167 }
16168
16169 // This will probably change for project fission, but currently this document
16170 // and the opener are on the same process. In the future, we should make this
16171 // part async.
16172 nsPIDOMWindowInner* inner = GetInnerWindow();
16173 if (NS_WARN_IF(!inner)) {
16174 return;
16175 }
16176
16177 uint32_t cookieBehavior = CookieJarSettings()->GetCookieBehavior();
16178 if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
16179 cookieBehavior ==
16180 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
16181 // We care about first-party tracking resources only.
16182 if (!nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) {
16183 return;
16184 }
16185 } else {
16186 MOZ_ASSERT(net::CookieJarSettings::IsRejectThirdPartyWithExceptions(
16187 cookieBehavior));
16188 }
16189
16190 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16191 if (NS_WARN_IF(!outer)) {
16192 return;
16193 }
16194
16195 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext();
16196 if (!openerBC) {
16197 // No opener.
16198 return;
16199 }
16200
16201 // We want to ensure the following check works for both fission mode and
16202 // non-fission mode:
16203 // "If the opener is not a 3rd party and if this window is not a 3rd party
16204 // with respect to the opener, we should not continue."
16205 //
16206 // In non-fission mode, the opener and the opened window are in the same
16207 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check.
16208 // In fission mode, if this window is not a 3rd party with respect to the
16209 // opener, they must be in the same process, so we can still use
16210 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd
16211 // party.
16212 if (openerBC->IsInProcess()) {
16213 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow();
16214 if (NS_WARN_IF(!outerOpener)) {
16215 return;
16216 }
16217
16218 nsCOMPtr<nsPIDOMWindowInner> openerInner =
16219 outerOpener->GetCurrentInnerWindow();
16220 if (NS_WARN_IF(!openerInner)) {
16221 return;
16222 }
16223
16224 RefPtr<Document> openerDocument = openerInner->GetExtantDoc();
16225 if (NS_WARN_IF(!openerDocument)) {
16226 return;
16227 }
16228
16229 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI();
16230 if (NS_WARN_IF(!openerURI)) {
16231 return;
16232 }
16233
16234 // If the opener is not a 3rd party and if this window is not
16235 // a 3rd party with respect to the opener, we should not continue.
16236 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) &&
16237 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) {
16238 return;
16239 }
16240 }
16241
16242 // We don't care when the asynchronous work finishes here.
16243 Unused << ContentBlocking::AllowAccessFor(
16244 NodePrincipal(), openerBC,
16245 ContentBlockingNotifier::eOpenerAfterUserInteraction);
16246 }
16247
16248 namespace {
16249
16250 // Documents can stay alive for days. We don't want to update the permission
16251 // value at any user-interaction, and, using a timer triggered any X seconds
16252 // should be good enough. 'X' is taken from
16253 // privacy.userInteraction.document.interval pref.
16254 // We also want to store the user-interaction before shutting down, and, for
16255 // this reason, this class implements nsIAsyncShutdownBlocker interface.
16256 class UserInteractionTimer final : public Runnable,
16257 public nsITimerCallback,
16258 public nsIAsyncShutdownBlocker {
16259 public:
16260 NS_DECL_ISUPPORTS_INHERITED
16261
UserInteractionTimer(Document * aDocument)16262 explicit UserInteractionTimer(Document* aDocument)
16263 : Runnable("UserInteractionTimer"),
16264 mPrincipal(aDocument->NodePrincipal()),
16265 mDocument(do_GetWeakReference(aDocument)) {
16266 static int32_t userInteractionTimerId = 0;
16267 // Blocker names must be unique. Let's create it now because when needed,
16268 // the document could be already gone.
16269 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p",
16270 ++userInteractionTimerId, aDocument);
16271 }
16272
16273 // Runnable interface
16274
16275 NS_IMETHOD
Run()16276 Run() override {
16277 uint32_t interval =
16278 StaticPrefs::privacy_userInteraction_document_interval();
16279 if (!interval) {
16280 return NS_OK;
16281 }
16282
16283 RefPtr<UserInteractionTimer> self = this;
16284 auto raii =
16285 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); });
16286
16287 nsresult rv = NS_NewTimerWithCallback(
16288 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT);
16289 NS_ENSURE_SUCCESS(rv, NS_OK);
16290
16291 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16292 NS_ENSURE_TRUE(!!phase, NS_OK);
16293
16294 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
16295 __LINE__, u"UserInteractionTimer shutdown"_ns);
16296 NS_ENSURE_SUCCESS(rv, NS_OK);
16297
16298 raii.release();
16299 return NS_OK;
16300 }
16301
16302 // nsITimerCallback interface
16303
16304 NS_IMETHOD
Notify(nsITimer * aTimer)16305 Notify(nsITimer* aTimer) override {
16306 StoreUserInteraction();
16307 return NS_OK;
16308 }
16309
16310 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
16311 using nsINamed::GetName;
16312 #endif
16313
16314 // nsIAsyncShutdownBlocker interface
16315
16316 NS_IMETHOD
GetName(nsAString & aName)16317 GetName(nsAString& aName) override {
16318 aName = mBlockerName;
16319 return NS_OK;
16320 }
16321
16322 NS_IMETHOD
BlockShutdown(nsIAsyncShutdownClient * aClient)16323 BlockShutdown(nsIAsyncShutdownClient* aClient) override {
16324 CancelTimerAndStoreUserInteraction();
16325 return NS_OK;
16326 }
16327
16328 NS_IMETHOD
GetState(nsIPropertyBag **)16329 GetState(nsIPropertyBag**) override { return NS_OK; }
16330
16331 private:
16332 ~UserInteractionTimer() = default;
16333
StoreUserInteraction()16334 void StoreUserInteraction() {
16335 // Remove the shutting down blocker
16336 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
16337 if (phase) {
16338 phase->RemoveBlocker(this);
16339 }
16340
16341 // If the document is not gone, let's reset its timer flag.
16342 nsCOMPtr<Document> document = do_QueryReferent(mDocument);
16343 if (document) {
16344 ContentBlockingUserInteraction::Observe(mPrincipal);
16345 document->ResetUserInteractionTimer();
16346 }
16347 }
16348
CancelTimerAndStoreUserInteraction()16349 void CancelTimerAndStoreUserInteraction() {
16350 if (mTimer) {
16351 mTimer->Cancel();
16352 mTimer = nullptr;
16353 }
16354
16355 StoreUserInteraction();
16356 }
16357
GetShutdownPhase()16358 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
16359 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
16360 NS_ENSURE_TRUE(!!svc, nullptr);
16361
16362 nsCOMPtr<nsIAsyncShutdownClient> phase;
16363 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
16364 NS_ENSURE_SUCCESS(rv, nullptr);
16365
16366 return phase.forget();
16367 }
16368
16369 nsCOMPtr<nsIPrincipal> mPrincipal;
16370 nsWeakPtr mDocument;
16371
16372 nsCOMPtr<nsITimer> mTimer;
16373
16374 nsString mBlockerName;
16375 };
16376
16377 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback,
16378 nsIAsyncShutdownBlocker)
16379
16380 } // namespace
16381
MaybeStoreUserInteractionAsPermission()16382 void Document::MaybeStoreUserInteractionAsPermission() {
16383 // We care about user-interaction stored only for top-level documents
16384 // and documents with access to the Storage Access API
16385 if (!IsTopLevelContentDocument()) {
16386 bool hasSA;
16387 nsresult rv = HasStorageAccessSync(hasSA);
16388 if (NS_FAILED(rv) || !hasSA) {
16389 return;
16390 }
16391 }
16392
16393 if (!mUserHasInteracted) {
16394 // First interaction, let's store this info now.
16395 ContentBlockingUserInteraction::Observe(NodePrincipal());
16396 return;
16397 }
16398
16399 if (mHasUserInteractionTimerScheduled) {
16400 return;
16401 }
16402
16403 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this);
16404 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500,
16405 EventQueuePriority::Idle);
16406 if (NS_WARN_IF(NS_FAILED(rv))) {
16407 return;
16408 }
16409
16410 // This value will be reset by the timer.
16411 mHasUserInteractionTimerScheduled = true;
16412 }
16413
ResetUserInteractionTimer()16414 void Document::ResetUserInteractionTimer() {
16415 mHasUserInteractionTimerScheduled = false;
16416 }
16417
IsExtensionPage() const16418 bool Document::IsExtensionPage() const {
16419 return Preferences::GetBool("media.autoplay.allow-extension-background-pages",
16420 true) &&
16421 BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
16422 }
16423
16424 /**
16425 * Retrieves the classification of the Flash plugins in the document based on
16426 * the classification lists. For more information, see
16427 * toolkit/components/url-classifier/flash-block-lists.rst
16428 */
DocumentFlashClassification()16429 FlashClassification Document::DocumentFlashClassification() {
16430 // Disable flash blocking when fission is enabled(See Bug 1584931).
16431 const auto fnIsFlashBlockingEnabled = [] {
16432 return StaticPrefs::plugins_flashBlock_enabled() && !FissionAutostart();
16433 };
16434
16435 // If neither pref is on, skip the null-principal and principal URI checks.
16436 if (!StaticPrefs::plugins_http_https_only() && !fnIsFlashBlockingEnabled()) {
16437 return FlashClassification::Unknown;
16438 }
16439
16440 if (!NodePrincipal()->GetIsContentPrincipal()) {
16441 return FlashClassification::Denied;
16442 }
16443
16444 if (StaticPrefs::plugins_http_https_only()) {
16445 // Only allow plugins for documents from an HTTP/HTTPS origin. This should
16446 // allow dependent data: URIs to load plugins, but not:
16447 // * chrome documents
16448 // * "bare" data: loads
16449 // * FTP/gopher/file
16450
16451 if (!(NodePrincipal()->SchemeIs("http") ||
16452 NodePrincipal()->SchemeIs("https"))) {
16453 return FlashClassification::Denied;
16454 }
16455 }
16456
16457 // If flash blocking is disabled, it is equivalent to all sites being
16458 // on neither list.
16459 if (!fnIsFlashBlockingEnabled()) {
16460 return FlashClassification::Unknown;
16461 }
16462
16463 if (mFlashClassification == FlashClassification::Unknown) {
16464 mFlashClassification = DocumentFlashClassificationInternal();
16465 }
16466
16467 return mFlashClassification;
16468 }
16469
AddResizeObserver(ResizeObserver & aObserver)16470 void Document::AddResizeObserver(ResizeObserver& aObserver) {
16471 if (!mResizeObserverController) {
16472 mResizeObserverController = MakeUnique<ResizeObserverController>(this);
16473 }
16474 mResizeObserverController->AddResizeObserver(aObserver);
16475 }
16476
RemoveResizeObserver(ResizeObserver & aObserver)16477 void Document::RemoveResizeObserver(ResizeObserver& aObserver) {
16478 MOZ_DIAGNOSTIC_ASSERT(mResizeObserverController, "No controller?");
16479 if (MOZ_UNLIKELY(!mResizeObserverController)) {
16480 return;
16481 }
16482 mResizeObserverController->RemoveResizeObserver(aObserver);
16483 }
16484
GetPermissionDelegateHandler()16485 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() {
16486 if (!mPermissionDelegateHandler) {
16487 mPermissionDelegateHandler =
16488 mozilla::MakeAndAddRef<PermissionDelegateHandler>(this);
16489 }
16490
16491 if (!mPermissionDelegateHandler->Initialize()) {
16492 mPermissionDelegateHandler = nullptr;
16493 }
16494
16495 return mPermissionDelegateHandler;
16496 }
16497
ScheduleResizeObserversNotification() const16498 void Document::ScheduleResizeObserversNotification() const {
16499 if (!mResizeObserverController) {
16500 return;
16501 }
16502
16503 mResizeObserverController->ScheduleNotification();
16504 }
16505
16506 /**
16507 * Initializes |mIsThirdPartyForFlashClassifier| if necessary and returns its
16508 * value. The value returned represents whether this document should be
16509 * considered Third-Party.
16510 *
16511 * A top-level document cannot be a considered Third-Party; only subdocuments
16512 * may. For a subdocument to be considered Third-Party, it must meet ANY ONE
16513 * of the following requirements:
16514 * - The document's parent is Third-Party
16515 * - The document has a different scheme (http/https) than its parent document
16516 * - The document's domain and subdomain do not match those of its parent
16517 * document.
16518 *
16519 * If there is an error in determining whether the document is Third-Party,
16520 * it will be assumed to be Third-Party for security reasons.
16521 */
IsThirdPartyForFlashClassifier()16522 bool Document::IsThirdPartyForFlashClassifier() {
16523 if (mIsThirdPartyForFlashClassifier.isSome()) {
16524 return mIsThirdPartyForFlashClassifier.value();
16525 }
16526
16527 BrowsingContext* browsingContext = this->GetBrowsingContext();
16528 if (!browsingContext) {
16529 mIsThirdPartyForFlashClassifier.emplace(true);
16530 return mIsThirdPartyForFlashClassifier.value();
16531 }
16532
16533 if (browsingContext->IsTop()) {
16534 mIsThirdPartyForFlashClassifier.emplace(false);
16535 return mIsThirdPartyForFlashClassifier.value();
16536 }
16537
16538 nsCOMPtr<Document> parentDocument = GetInProcessParentDocument();
16539 if (!parentDocument) {
16540 // Failure
16541 mIsThirdPartyForFlashClassifier.emplace(true);
16542 return mIsThirdPartyForFlashClassifier.value();
16543 }
16544
16545 if (parentDocument->IsThirdPartyForFlashClassifier()) {
16546 mIsThirdPartyForFlashClassifier.emplace(true);
16547 return mIsThirdPartyForFlashClassifier.value();
16548 }
16549
16550 nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
16551 nsCOMPtr<nsIPrincipal> parentPrincipal = parentDocument->GetPrincipal();
16552
16553 bool principalsMatch = false;
16554 nsresult rv = principal->Equals(parentPrincipal, &principalsMatch);
16555
16556 if (NS_WARN_IF(NS_FAILED(rv))) {
16557 // Failure
16558 mIsThirdPartyForFlashClassifier.emplace(true);
16559 return mIsThirdPartyForFlashClassifier.value();
16560 }
16561
16562 if (!principalsMatch) {
16563 mIsThirdPartyForFlashClassifier.emplace(true);
16564 return mIsThirdPartyForFlashClassifier.value();
16565 }
16566
16567 // Fall-through. Document is not a Third-Party Document.
16568 mIsThirdPartyForFlashClassifier.emplace(false);
16569 return mIsThirdPartyForFlashClassifier.value();
16570 }
16571
DocumentFlashClassificationInternal()16572 FlashClassification Document::DocumentFlashClassificationInternal() {
16573 FlashClassification classification = FlashClassification::Unknown;
16574
16575 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(GetChannel());
16576 if (httpChannel) {
16577 nsIHttpChannel::FlashPluginState state = nsIHttpChannel::FlashPluginUnknown;
16578 httpChannel->GetFlashPluginState(&state);
16579
16580 // Allow unknown children to inherit allowed status from parent, but do not
16581 // allow denied children to do so.
16582
16583 if (state == nsIHttpChannel::FlashPluginDeniedInSubdocuments &&
16584 IsThirdPartyForFlashClassifier()) {
16585 return FlashClassification::Denied;
16586 }
16587
16588 if (state == nsIHttpChannel::FlashPluginDenied) {
16589 return FlashClassification::Denied;
16590 }
16591
16592 if (state == nsIHttpChannel::FlashPluginAllowed) {
16593 classification = FlashClassification::Allowed;
16594 }
16595 }
16596
16597 if (IsTopLevelContentDocument()) {
16598 return classification;
16599 }
16600
16601 Document* parentDocument = GetInProcessParentDocument();
16602 if (!parentDocument) {
16603 return FlashClassification::Denied;
16604 }
16605
16606 FlashClassification parentClassification =
16607 parentDocument->DocumentFlashClassification();
16608
16609 if (parentClassification == FlashClassification::Denied) {
16610 return FlashClassification::Denied;
16611 }
16612
16613 // Allow unknown children to inherit allowed status from parent, but
16614 // do not allow denied children to do so.
16615 if (classification == FlashClassification::Unknown &&
16616 parentClassification == FlashClassification::Allowed) {
16617 return FlashClassification::Allowed;
16618 }
16619
16620 return classification;
16621 }
16622
ClearStaleServoData()16623 void Document::ClearStaleServoData() {
16624 DocumentStyleRootIterator iter(this);
16625 while (Element* root = iter.GetNextStyleRoot()) {
16626 RestyleManager::ClearServoDataFromSubtree(root);
16627 }
16628 }
16629
GetSelection(ErrorResult & aRv)16630 Selection* Document::GetSelection(ErrorResult& aRv) {
16631 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
16632 if (!window) {
16633 return nullptr;
16634 }
16635
16636 if (!window->IsCurrentInnerWindow()) {
16637 return nullptr;
16638 }
16639
16640 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
16641 }
16642
HasStorageAccessSync(bool & aHasStorageAccess)16643 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) {
16644 if (NodePrincipal()->GetIsNullPrincipal()) {
16645 aHasStorageAccess = false;
16646 return NS_OK;
16647 }
16648
16649 if (CookieJarSettings()->GetBlockingAllContexts()) {
16650 aHasStorageAccess = false;
16651 return NS_OK;
16652 }
16653
16654 if (IsTopLevelContentDocument()) {
16655 aHasStorageAccess = true;
16656 return NS_OK;
16657 }
16658
16659 RefPtr<BrowsingContext> bc = GetBrowsingContext();
16660 if (!bc) {
16661 aHasStorageAccess = false;
16662 return NS_OK;
16663 }
16664
16665 RefPtr<BrowsingContext> topBC = bc->Top();
16666 // We check if the document is a first-party document here by testing if the
16667 // top-level window is same-origin. In non-Fission mode, we can directly get
16668 // the top-level window through the top browsing context since it should be
16669 // in-process. And test their principals.
16670 //
16671 // In Fission mode, we can also directly get the top-level window. If we
16672 // cannot get it, this means the top-level window is cross-origin. Then, we
16673 // know our answer.
16674 if (auto* topOuterWindow = topBC->GetDOMWindow()) {
16675 if (nsGlobalWindowOuter::Cast(topOuterWindow)
16676 ->GetPrincipal()
16677 ->Equals(NodePrincipal())) {
16678 aHasStorageAccess = true;
16679 return NS_OK;
16680 }
16681 }
16682
16683 nsPIDOMWindowInner* inner = GetInnerWindow();
16684 nsGlobalWindowOuter* outer = nullptr;
16685 NS_ENSURE_TRUE(inner, NS_ERROR_FAILURE);
16686 outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16687 NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE);
16688 aHasStorageAccess = outer->IsStorageAccessPermissionGranted();
16689 return NS_OK;
16690 }
16691
HasStorageAccess(mozilla::ErrorResult & aRv)16692 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess(
16693 mozilla::ErrorResult& aRv) {
16694 nsIGlobalObject* global = GetScopeObject();
16695 if (!global) {
16696 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
16697 return nullptr;
16698 }
16699
16700 RefPtr<Promise> promise =
16701 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
16702 if (aRv.Failed()) {
16703 return nullptr;
16704 }
16705
16706 bool hasStorageAccess;
16707 nsresult rv = HasStorageAccessSync(hasStorageAccess);
16708 if (NS_FAILED(rv)) {
16709 promise->MaybeRejectWithUndefined();
16710 } else {
16711 promise->MaybeResolve(hasStorageAccess);
16712 }
16713
16714 return promise.forget();
16715 }
16716
16717 RefPtr<Document::GetContentBlockingEventsPromise>
GetContentBlockingEvents()16718 Document::GetContentBlockingEvents() {
16719 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
16720 if (!inner) {
16721 return nullptr;
16722 }
16723
16724 RefPtr<WindowGlobalChild> wgc = inner->GetWindowGlobalChild();
16725 if (!wgc) {
16726 return nullptr;
16727 }
16728
16729 return wgc->SendGetContentBlockingEvents()->Then(
16730 GetCurrentSerialEventTarget(), __func__,
16731 [](const WindowGlobalChild::GetContentBlockingEventsPromise::
16732 ResolveOrRejectValue& aValue) {
16733 if (aValue.IsResolve()) {
16734 return Document::GetContentBlockingEventsPromise::CreateAndResolve(
16735 aValue.ResolveValue(), __func__);
16736 }
16737
16738 return Document::GetContentBlockingEventsPromise::CreateAndReject(
16739 false, __func__);
16740 });
16741 }
16742
RequestStorageAccess(mozilla::ErrorResult & aRv)16743 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
16744 mozilla::ErrorResult& aRv) {
16745 nsIGlobalObject* global = GetScopeObject();
16746 if (!global) {
16747 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
16748 return nullptr;
16749 }
16750
16751 RefPtr<Promise> promise = Promise::Create(global, aRv);
16752 if (aRv.Failed()) {
16753 return nullptr;
16754 }
16755
16756 // Window doesn't have user activation, reject.
16757 if (!this->HasValidTransientUserGestureActivation()) {
16758 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
16759 nsLiteralCString("requestStorageAccess"),
16760 this, nsContentUtils::eDOM_PROPERTIES,
16761 "RequestStorageAccessUserGesture");
16762 promise->MaybeRejectWithUndefined();
16763 return promise.forget();
16764 }
16765
16766 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
16767 if (!inner) {
16768 this->ConsumeTransientUserGestureActivation();
16769 promise->MaybeRejectWithUndefined();
16770 return promise.forget();
16771 }
16772
16773 // Step 0. If the browser forbids any storage access, reject.
16774 if (CookieJarSettings()->GetBlockingAllContexts()) {
16775 this->ConsumeTransientUserGestureActivation();
16776 promise->MaybeRejectWithUndefined();
16777 return promise.forget();
16778 }
16779
16780 // Step 1. If the document already has been granted access, resolve.
16781 RefPtr<nsGlobalWindowOuter> outer =
16782 nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
16783 if (!outer) {
16784 this->ConsumeTransientUserGestureActivation();
16785 promise->MaybeRejectWithUndefined();
16786 return promise.forget();
16787 }
16788
16789 if (outer->IsStorageAccessPermissionGranted()) {
16790 promise->MaybeResolveWithUndefined();
16791 return promise.forget();
16792 }
16793
16794 // Step 2. If the document has a null origin, reject.
16795 if (NodePrincipal()->GetIsNullPrincipal()) {
16796 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
16797 nsLiteralCString("requestStorageAccess"),
16798 this, nsContentUtils::eDOM_PROPERTIES,
16799 "RequestStorageAccessNullPrincipal");
16800 this->ConsumeTransientUserGestureActivation();
16801 promise->MaybeRejectWithUndefined();
16802 return promise.forget();
16803 }
16804
16805 RefPtr<BrowsingContext> bc = GetBrowsingContext();
16806 if (!bc) {
16807 this->ConsumeTransientUserGestureActivation();
16808 promise->MaybeRejectWithUndefined();
16809 return promise.forget();
16810 }
16811
16812 // Only enforce third-party checks when there is a reason to enforce them.
16813 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
16814 // Step 3. If the document's frame is the main frame, resolve.
16815 if (IsTopLevelContentDocument()) {
16816 promise->MaybeResolveWithUndefined();
16817 return promise.forget();
16818 }
16819
16820 // Step 4. If the sub frame's origin is equal to the main frame's, resolve.
16821
16822 // In fission, if the sub frame's origin differs from the main frame's
16823 // origin, they will be in different processes. We use IsInProcess()
16824 // check here to deterimine whether they have the same origin. In
16825 // non-fission mode, it is always in-process so we need to compare their
16826 // principals.
16827 if (bc->Top()->IsInProcess()) {
16828 nsCOMPtr<nsPIDOMWindowOuter> topOuter = bc->Top()->GetDOMWindow();
16829 if (!topOuter) {
16830 this->ConsumeTransientUserGestureActivation();
16831 promise->MaybeRejectWithUndefined();
16832 return promise.forget();
16833 }
16834
16835 nsCOMPtr<Document> topLevelDoc = topOuter->GetExtantDoc();
16836 if (!topLevelDoc) {
16837 this->ConsumeTransientUserGestureActivation();
16838 promise->MaybeRejectWithUndefined();
16839 return promise.forget();
16840 }
16841
16842 if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
16843 promise->MaybeResolveWithUndefined();
16844 return promise.forget();
16845 }
16846 }
16847 }
16848
16849 // Step 5. If the sub frame is not sandboxed, skip to step 7.
16850 // Step 6. If the sub frame doesn't have the token
16851 // "allow-storage-access-by-user-activation", reject.
16852 if (StorageAccessSandboxed()) {
16853 nsContentUtils::ReportToConsole(
16854 nsIScriptError::errorFlag, nsLiteralCString("requestStorageAccess"),
16855 this, nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessSandboxed");
16856 this->ConsumeTransientUserGestureActivation();
16857 promise->MaybeRejectWithUndefined();
16858 return promise.forget();
16859 }
16860
16861 // Step 7. If the sub frame's parent frame is not the top frame, reject.
16862 RefPtr<BrowsingContext> parentBC = bc->GetParent();
16863 if (parentBC && !parentBC->IsTopContent()) {
16864 nsContentUtils::ReportToConsole(
16865 nsIScriptError::errorFlag, nsLiteralCString("requestStorageAccess"),
16866 this, nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessNested");
16867 this->ConsumeTransientUserGestureActivation();
16868 promise->MaybeRejectWithUndefined();
16869 return promise.forget();
16870 }
16871
16872 // Step 9. Check any additional rules that the browser has.
16873 // Examples: skip-lists, on-device classification,
16874 // user settings, anti-clickjacking heuristics, or prompting the
16875 // user for explicit permission. Reject if some rule is not fulfilled.
16876
16877 if (CookieJarSettings()->GetBlockingAllThirdPartyContexts()) {
16878 this->ConsumeTransientUserGestureActivation();
16879 promise->MaybeRejectWithUndefined();
16880 return promise.forget();
16881 }
16882
16883 if (CookieJarSettings()->GetRejectThirdPartyContexts()) {
16884 // Only do something special for third-party tracking content.
16885 uint32_t antiTrackingRejectedReason = 0;
16886 if (StorageDisabledByAntiTracking(this, nullptr,
16887 antiTrackingRejectedReason)) {
16888 // If storage is disabled because of a custom cookie permission for the
16889 // site, reject.
16890 if (antiTrackingRejectedReason ==
16891 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) {
16892 this->ConsumeTransientUserGestureActivation();
16893 promise->MaybeRejectWithUndefined();
16894 return promise.forget();
16895 }
16896
16897 // Note: If this has returned true, the top-level document is guaranteed
16898 // to not be on the Content Blocking allow list.
16899 MOZ_ASSERT(!CookieJarSettings()->GetIsOnContentBlockingAllowList());
16900
16901 RefPtr<Document> self(this);
16902
16903 auto performFinalChecks =
16904 [inner,
16905 self]() -> RefPtr<ContentBlocking::StorageAccessFinalCheckPromise> {
16906 RefPtr<ContentBlocking::StorageAccessFinalCheckPromise::Private> p =
16907 new ContentBlocking::StorageAccessFinalCheckPromise::Private(
16908 __func__);
16909 RefPtr<StorageAccessPermissionRequest> sapr =
16910 StorageAccessPermissionRequest::Create(
16911 inner,
16912 // Allow
16913 [p] {
16914 Telemetry::AccumulateCategorical(
16915 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
16916 p->Resolve(ContentBlocking::eAllow, __func__);
16917 },
16918 // Block
16919 [p] {
16920 Telemetry::AccumulateCategorical(
16921 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
16922 p->Reject(false, __func__);
16923 });
16924
16925 using PromptResult = ContentPermissionRequestBase::PromptResult;
16926 PromptResult pr = sapr->CheckPromptPrefs();
16927
16928 if (pr == PromptResult::Pending) {
16929 // We're about to show a prompt, record the request attempt
16930 Telemetry::AccumulateCategorical(
16931 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
16932 }
16933
16934 self->AutomaticStorageAccessPermissionCanBeGranted(true)->Then(
16935 GetCurrentSerialEventTarget(), __func__,
16936 [p, pr, sapr,
16937 inner](const AutomaticStorageAccessPermissionGrantPromise::
16938 ResolveOrRejectValue& aValue) -> void {
16939 // Make a copy because we can't modified copy-captured lambda
16940 // variables.
16941 PromptResult pr2 = pr;
16942
16943 bool storageAccessCanBeGrantedAutomatically =
16944 aValue.IsResolve() && aValue.ResolveValue();
16945
16946 bool autoGrant = false;
16947 if (pr2 == PromptResult::Pending &&
16948 storageAccessCanBeGrantedAutomatically) {
16949 pr2 = PromptResult::Granted;
16950 autoGrant = true;
16951
16952 Telemetry::AccumulateCategorical(
16953 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
16954 AllowAutomatically);
16955 }
16956
16957 if (pr2 != PromptResult::Pending) {
16958 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
16959 pr2 == PromptResult::Denied);
16960 if (pr2 == PromptResult::Granted) {
16961 ContentBlocking::StorageAccessPromptChoices choice =
16962 ContentBlocking::eAllow;
16963 if (autoGrant) {
16964 choice = ContentBlocking::eAllowAutoGrant;
16965 }
16966 if (!autoGrant) {
16967 p->Resolve(choice, __func__);
16968 } else {
16969 sapr->MaybeDelayAutomaticGrants()->Then(
16970 GetCurrentSerialEventTarget(), __func__,
16971 [p, choice] { p->Resolve(choice, __func__); },
16972 [p] { p->Reject(false, __func__); });
16973 }
16974 return;
16975 }
16976 p->Reject(false, __func__);
16977 return;
16978 }
16979
16980 sapr->RequestDelayedTask(
16981 inner->EventTargetFor(TaskCategory::Other),
16982 ContentPermissionRequestBase::DelayedTaskType::Request);
16983 });
16984
16985 return p;
16986 };
16987
16988 // Consume user activation before entering the async part of this method.
16989 // This prevents usage of other transient activation-gated APIs.
16990 this->ConsumeTransientUserGestureActivation();
16991
16992 ContentBlocking::AllowAccessFor(
16993 NodePrincipal(), bc, ContentBlockingNotifier::eStorageAccessAPI,
16994 performFinalChecks)
16995 ->Then(
16996 GetCurrentSerialEventTarget(), __func__,
16997 [self, outer, promise] {
16998 // Step 10. Grant the document access to cookies and store
16999 // that fact for
17000 // the purposes of future calls to
17001 // hasStorageAccess() and requestStorageAccess().
17002 outer->SetStorageAccessPermissionGranted(true);
17003 self->NotifyUserGestureActivation();
17004 promise->MaybeResolveWithUndefined();
17005 },
17006 [outer, promise] {
17007 outer->SetStorageAccessPermissionGranted(false);
17008 promise->MaybeRejectWithUndefined();
17009 });
17010
17011 return promise.forget();
17012 }
17013 }
17014
17015 outer->SetStorageAccessPermissionGranted(true);
17016 promise->MaybeResolveWithUndefined();
17017 return promise.forget();
17018 }
17019
RequestStorageAccessForOrigin(const nsAString & aThirdPartyOrigin,const bool aRequireUserActivation,mozilla::ErrorResult & aRv)17020 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
17021 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation,
17022 mozilla::ErrorResult& aRv) {
17023 nsIGlobalObject* global = GetScopeObject();
17024 if (!global) {
17025 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
17026 return nullptr;
17027 }
17028
17029 RefPtr<Promise> promise = Promise::Create(global, aRv);
17030 if (aRv.Failed()) {
17031 return nullptr;
17032 }
17033
17034 // Window doesn't have user activation, reject.
17035 bool hasUserActivation = this->HasValidTransientUserGestureActivation();
17036 if (aRequireUserActivation && !hasUserActivation) {
17037 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17038 nsLiteralCString("requestStorageAccess"),
17039 this, nsContentUtils::eDOM_PROPERTIES,
17040 "RequestStorageAccessUserGesture");
17041 promise->MaybeRejectWithUndefined();
17042 return promise.forget();
17043 }
17044
17045 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
17046 if (!inner) {
17047 this->ConsumeTransientUserGestureActivation();
17048 promise->MaybeRejectWithUndefined();
17049 return promise.forget();
17050 }
17051
17052 // We only allow request storage access for third-party origin from the
17053 // first-party context.
17054 if (AntiTrackingUtils::IsThirdPartyWindow(inner, nullptr)) {
17055 this->ConsumeTransientUserGestureActivation();
17056 promise->MaybeRejectWithUndefined();
17057 return promise.forget();
17058 }
17059
17060 // If the document has a null origin, reject.
17061 if (NodePrincipal()->GetIsNullPrincipal()) {
17062 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
17063 nsLiteralCString("requestStorageAccess"),
17064 this, nsContentUtils::eDOM_PROPERTIES,
17065 "RequestStorageAccessNullPrincipal");
17066 this->ConsumeTransientUserGestureActivation();
17067 promise->MaybeRejectWithUndefined();
17068 return promise.forget();
17069 }
17070
17071 nsCOMPtr<nsIURI> thirdPartyURI;
17072 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin);
17073 if (NS_WARN_IF(NS_FAILED(rv))) {
17074 aRv.Throw(rv);
17075 return nullptr;
17076 }
17077
17078 // If the browser forbids any storage access, reject.
17079 if (CookieJarSettings()->GetBlockingAllContexts()) {
17080 this->ConsumeTransientUserGestureActivation();
17081 promise->MaybeRejectWithUndefined();
17082 return promise.forget();
17083 }
17084
17085 // Only enforce third-party checks when there is a reason to enforce them.
17086 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) {
17087 // If the the thrid party origin is equal to the window's, resolve.
17088 if (NodePrincipal()->IsSameOrigin(thirdPartyURI)) {
17089 promise->MaybeResolveWithUndefined();
17090 return promise.forget();
17091 }
17092 }
17093
17094 // Check any additional rules that the browser has.
17095
17096 if (CookieJarSettings()->GetBlockingAllThirdPartyContexts()) {
17097 this->ConsumeTransientUserGestureActivation();
17098 promise->MaybeRejectWithUndefined();
17099 return promise.forget();
17100 }
17101
17102 if (CookieJarSettings()->GetRejectThirdPartyContexts()) {
17103 RefPtr<BrowsingContext> bc = GetBrowsingContext();
17104 if (!bc) {
17105 this->ConsumeTransientUserGestureActivation();
17106 promise->MaybeRejectWithUndefined();
17107 return promise.forget();
17108 }
17109
17110 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
17111 thirdPartyURI, NodePrincipal()->OriginAttributesRef());
17112
17113 if (!principal) {
17114 this->ConsumeTransientUserGestureActivation();
17115 promise->MaybeRejectWithUndefined();
17116 return promise.forget();
17117 }
17118
17119 RefPtr<Document> self(this);
17120
17121 // Consume user activation before entering the async part of this method.
17122 // This prevents usage of other transient activation-gated APIs.
17123 this->ConsumeTransientUserGestureActivation();
17124
17125 auto performFinalChecks = [inner, self, principal, hasUserActivation]() {
17126 RefPtr<ContentBlocking::StorageAccessFinalCheckPromise::Private> p =
17127 new ContentBlocking::StorageAccessFinalCheckPromise::Private(
17128 __func__);
17129 RefPtr<StorageAccessPermissionRequest> sapr =
17130 StorageAccessPermissionRequest::Create(
17131 inner, principal,
17132 // Allow
17133 [p] {
17134 Telemetry::AccumulateCategorical(
17135 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Allow);
17136 p->Resolve(ContentBlocking::eAllow, __func__);
17137 },
17138 // Block
17139 [p] {
17140 Telemetry::AccumulateCategorical(
17141 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Deny);
17142 p->Reject(false, __func__);
17143 });
17144
17145 using PromptResult = ContentPermissionRequestBase::PromptResult;
17146 PromptResult pr = sapr->CheckPromptPrefs();
17147
17148 if (pr == PromptResult::Pending) {
17149 // We're about to show a prompt, record the request attempt
17150 Telemetry::AccumulateCategorical(
17151 Telemetry::LABELS_STORAGE_ACCESS_API_UI::Request);
17152 }
17153
17154 self->AutomaticStorageAccessPermissionCanBeGranted(hasUserActivation)
17155 ->Then(GetCurrentSerialEventTarget(), __func__,
17156 [p, pr, sapr,
17157 inner](const AutomaticStorageAccessPermissionGrantPromise::
17158 ResolveOrRejectValue& aValue) -> void {
17159 // Make a copy because we can't modified copy-captured lambda
17160 // variables.
17161 PromptResult pr2 = pr;
17162
17163 bool storageAccessCanBeGrantedAutomatically =
17164 aValue.IsResolve() && aValue.ResolveValue();
17165
17166 bool autoGrant = false;
17167 if (pr2 == PromptResult::Pending &&
17168 storageAccessCanBeGrantedAutomatically) {
17169 pr2 = PromptResult::Granted;
17170 autoGrant = true;
17171
17172 Telemetry::AccumulateCategorical(
17173 Telemetry::LABELS_STORAGE_ACCESS_API_UI::
17174 AllowAutomatically);
17175 }
17176
17177 if (pr2 != PromptResult::Pending) {
17178 MOZ_ASSERT_IF(pr2 != PromptResult::Granted,
17179 pr2 == PromptResult::Denied);
17180 if (pr2 == PromptResult::Granted) {
17181 ContentBlocking::StorageAccessPromptChoices choice =
17182 ContentBlocking::eAllow;
17183 if (autoGrant) {
17184 choice = ContentBlocking::eAllowAutoGrant;
17185 }
17186 if (!autoGrant) {
17187 p->Resolve(choice, __func__);
17188 } else {
17189 sapr->MaybeDelayAutomaticGrants()->Then(
17190 GetCurrentSerialEventTarget(), __func__,
17191 [p, choice] { p->Resolve(choice, __func__); },
17192 [p] { p->Reject(false, __func__); });
17193 }
17194 return;
17195 }
17196 p->Reject(false, __func__);
17197 return;
17198 }
17199
17200 sapr->RequestDelayedTask(
17201 inner->EventTargetFor(TaskCategory::Other),
17202 ContentPermissionRequestBase::DelayedTaskType::Request);
17203 });
17204
17205 return p;
17206 };
17207
17208 // Only do something special for the third party that storage has been
17209 // disabled by anti-tracking feature.
17210 AsyncStorageDisabledByAntiTracking(bc, principal)
17211 ->Then(
17212 GetCurrentSerialEventTarget(), __func__,
17213 [performFinalChecks, promise, bc, principal,
17214 self](AsyncStorageDisabledByAntiTrackingPromise::
17215 ResolveOrRejectValue&& aValue) {
17216 if (aValue.IsReject()) {
17217 // Storage was enabled by anti-tracking feature.
17218 return ContentBlocking::StorageAccessPermissionGrantPromise::
17219 CreateAndResolve(0, __func__);
17220 }
17221
17222 MOZ_ASSERT(aValue.IsResolve());
17223 // Storage was disabled by anti-tracking feature.
17224
17225 // If the storage was disabled by the cookie permission, we don't
17226 // bother to show the prompt.
17227 uint32_t rejectReason = aValue.ResolveValue();
17228 if (rejectReason ==
17229 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) {
17230 return ContentBlocking::StorageAccessPermissionGrantPromise::
17231 CreateAndReject(true, __func__);
17232 ;
17233 }
17234
17235 MOZ_ASSERT(!self->CookieJarSettings()
17236 ->GetIsOnContentBlockingAllowList());
17237
17238 return ContentBlocking::AllowAccessFor(
17239 principal, bc,
17240 ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI,
17241 performFinalChecks);
17242 })
17243 ->Then(
17244 GetCurrentSerialEventTarget(), __func__,
17245 [self, promise] {
17246 self->NotifyUserGestureActivation();
17247 promise->MaybeResolveWithUndefined();
17248 },
17249 [promise] { promise->MaybeRejectWithUndefined(); });
17250
17251 return promise.forget();
17252 }
17253
17254 promise->MaybeResolveWithUndefined();
17255 return promise.forget();
17256 }
17257
17258 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation)17259 Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
17260 // requestStorageAccessForOrigin may not require user activation. If we don't
17261 // have user activation at this point we should always show the prompt.
17262 if (!hasUserActivation ||
17263 !StaticPrefs::privacy_antitracking_enableWebcompat()) {
17264 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
17265 false, __func__);
17266 }
17267 if (XRE_IsContentProcess()) {
17268 // In the content process, we need to ask the parent process to compute
17269 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix()
17270 // isn't accessible in the content process.
17271 ContentChild* cc = ContentChild::GetSingleton();
17272 MOZ_ASSERT(cc);
17273
17274 return cc
17275 ->SendAutomaticStorageAccessPermissionCanBeGranted(
17276 IPC::Principal(NodePrincipal()))
17277 ->Then(GetCurrentSerialEventTarget(), __func__,
17278 [](const ContentChild::
17279 AutomaticStorageAccessPermissionCanBeGrantedPromise::
17280 ResolveOrRejectValue& aValue) {
17281 if (aValue.IsResolve()) {
17282 return AutomaticStorageAccessPermissionGrantPromise::
17283 CreateAndResolve(aValue.ResolveValue(), __func__);
17284 }
17285
17286 return AutomaticStorageAccessPermissionGrantPromise::
17287 CreateAndReject(false, __func__);
17288 });
17289 }
17290
17291 if (XRE_IsParentProcess()) {
17292 // In the parent process, we can directly compute this.
17293 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve(
17294 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()),
17295 __func__);
17296 }
17297
17298 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject(
17299 false, __func__);
17300 }
17301
AutomaticStorageAccessPermissionCanBeGranted(nsIPrincipal * aPrincipal)17302 bool Document::AutomaticStorageAccessPermissionCanBeGranted(
17303 nsIPrincipal* aPrincipal) {
17304 nsAutoCString prefix;
17305 AntiTrackingUtils::CreateStoragePermissionKey(aPrincipal, prefix);
17306
17307 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) {
17308 return false;
17309 }
17310
17311 PermissionManager* permManager = PermissionManager::GetInstance();
17312 if (NS_WARN_IF(!permManager)) {
17313 return false;
17314 }
17315
17316 using Permissions = nsTArray<RefPtr<nsIPermission>>;
17317 Permissions perms;
17318 nsresult rv = permManager->GetAllWithTypePrefix(prefix, perms);
17319 if (NS_WARN_IF(NS_FAILED(rv))) {
17320 return false;
17321 }
17322
17323 nsAutoCString prefix2(prefix);
17324 prefix2.Append('^');
17325 using Origins = nsTArray<nsCString>;
17326 Origins origins;
17327
17328 for (const auto& perm : perms) {
17329 nsAutoCString type;
17330 rv = perm->GetType(type);
17331 if (NS_WARN_IF(NS_FAILED(rv))) {
17332 return false;
17333 }
17334 // Let's make sure that we're not looking at a permission for
17335 // https://exampletracker.company when we mean to look for the
17336 // permission for https://exampletracker.com!
17337 if (type != prefix && StringHead(type, prefix2.Length()) != prefix2) {
17338 continue;
17339 }
17340
17341 nsCOMPtr<nsIPrincipal> principal;
17342 rv = perm->GetPrincipal(getter_AddRefs(principal));
17343 if (NS_WARN_IF(NS_FAILED(rv))) {
17344 return false;
17345 }
17346
17347 nsAutoCString origin;
17348 rv = principal->GetOrigin(origin);
17349 if (NS_WARN_IF(NS_FAILED(rv))) {
17350 return false;
17351 }
17352
17353 ToLowerCase(origin);
17354
17355 if (origins.IndexOf(origin) == Origins::NoIndex) {
17356 origins.AppendElement(origin);
17357 }
17358 }
17359
17360 nsCOMPtr<nsIBrowserUsage> bu = do_ImportModule(
17361 "resource:///modules/BrowserUsageTelemetry.jsm", fallible);
17362 if (NS_WARN_IF(!bu)) {
17363 return false;
17364 }
17365
17366 uint32_t uniqueDomainsVisitedInPast24Hours = 0;
17367 rv = bu->GetUniqueDomainsVisitedInPast24Hours(
17368 &uniqueDomainsVisitedInPast24Hours);
17369 if (NS_WARN_IF(NS_FAILED(rv))) {
17370 return false;
17371 }
17372
17373 // one percent of the number of top-levels origins visited in the current
17374 // session (but not to exceed 24 hours), or the value of the
17375 // dom.storage_access.max_concurrent_auto_grants preference, whichever is
17376 // higher.
17377 size_t maxConcurrentAutomaticGrants = std::max(
17378 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)),
17379 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()),
17380 0);
17381
17382 size_t originsThirdPartyHasAccessTo = origins.Length();
17383
17384 return StaticPrefs::dom_storage_access_auto_grants() &&
17385 originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants;
17386 }
17387
RecordNavigationTiming(ReadyState aReadyState)17388 void Document::RecordNavigationTiming(ReadyState aReadyState) {
17389 if (!XRE_IsContentProcess()) {
17390 return;
17391 }
17392 if (!IsTopLevelContentDocument()) {
17393 return;
17394 }
17395 // If we dont have the timing yet (mostly because the doc is still loading),
17396 // get it from docshell.
17397 RefPtr<nsDOMNavigationTiming> timing = mTiming;
17398 if (!timing) {
17399 if (!mDocumentContainer) {
17400 return;
17401 }
17402 timing = mDocumentContainer->GetNavigationTiming();
17403 if (!timing) {
17404 return;
17405 }
17406 }
17407 TimeStamp startTime = timing->GetNavigationStartTimeStamp();
17408 switch (aReadyState) {
17409 case READYSTATE_LOADING:
17410 if (!mDOMLoadingSet) {
17411 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_LOADING_MS,
17412 startTime);
17413 mDOMLoadingSet = true;
17414 }
17415 break;
17416 case READYSTATE_INTERACTIVE:
17417 if (!mDOMInteractiveSet) {
17418 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_INTERACTIVE_MS,
17419 startTime);
17420 mDOMInteractiveSet = true;
17421 }
17422 break;
17423 case READYSTATE_COMPLETE:
17424 if (!mDOMCompleteSet) {
17425 Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_COMPLETE_MS,
17426 startTime);
17427 mDOMCompleteSet = true;
17428 }
17429 break;
17430 default:
17431 NS_WARNING("Unexpected ReadyState value");
17432 break;
17433 }
17434 }
17435
ModuleScriptsEnabled()17436 bool Document::ModuleScriptsEnabled() {
17437 return nsContentUtils::IsChromeDoc(this) ||
17438 StaticPrefs::dom_moduleScripts_enabled();
17439 }
17440
ReportShadowDOMUsage()17441 void Document::ReportShadowDOMUsage() {
17442 nsPIDOMWindowInner* inner = GetInnerWindow();
17443 if (NS_WARN_IF(!inner)) {
17444 return;
17445 }
17446
17447 WindowContext* wc = inner->GetWindowContext();
17448 if (NS_WARN_IF(!wc || wc->IsDiscarded())) {
17449 return;
17450 }
17451
17452 WindowContext* topWc = wc->TopWindowContext();
17453 if (topWc->GetHasReportedShadowDOMUsage()) {
17454 return;
17455 }
17456
17457 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true));
17458 }
17459
17460 // static
StorageAccessSandboxed(uint32_t aSandboxFlags)17461 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) {
17462 return StaticPrefs::dom_storage_access_enabled() &&
17463 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0;
17464 }
17465
StorageAccessSandboxed() const17466 bool Document::StorageAccessSandboxed() const {
17467 return Document::StorageAccessSandboxed(GetSandboxFlags());
17468 }
17469
GetCachedSizes(nsTabSizes * aSizes)17470 bool Document::GetCachedSizes(nsTabSizes* aSizes) {
17471 if (mCachedTabSizeGeneration == 0 ||
17472 GetGeneration() != mCachedTabSizeGeneration) {
17473 return false;
17474 }
17475 aSizes->mDom += mCachedTabSizes.mDom;
17476 aSizes->mStyle += mCachedTabSizes.mStyle;
17477 aSizes->mOther += mCachedTabSizes.mOther;
17478 return true;
17479 }
17480
SetCachedSizes(nsTabSizes * aSizes)17481 void Document::SetCachedSizes(nsTabSizes* aSizes) {
17482 mCachedTabSizes.mDom = aSizes->mDom;
17483 mCachedTabSizes.mStyle = aSizes->mStyle;
17484 mCachedTabSizes.mOther = aSizes->mOther;
17485 mCachedTabSizeGeneration = GetGeneration();
17486 }
17487
GetContentLanguageAsAtomForStyle() const17488 already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
17489 nsAutoString contentLang;
17490 GetContentLanguage(contentLang);
17491 contentLang.StripWhitespace();
17492
17493 // Content-Language may be a comma-separated list of language codes,
17494 // in which case the HTML5 spec says to treat it as unknown
17495 if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
17496 return NS_Atomize(contentLang);
17497 }
17498
17499 return nullptr;
17500 }
17501
GetLanguageForStyle() const17502 already_AddRefed<nsAtom> Document::GetLanguageForStyle() const {
17503 RefPtr<nsAtom> lang = GetContentLanguageAsAtomForStyle();
17504 if (!lang) {
17505 lang = mLanguageFromCharset;
17506 }
17507 return lang.forget();
17508 }
17509
GetFontPrefsForLang(nsAtom * aLanguage,bool * aNeedsToCache) const17510 const LangGroupFontPrefs* Document::GetFontPrefsForLang(
17511 nsAtom* aLanguage, bool* aNeedsToCache) const {
17512 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset.get();
17513 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache);
17514 }
17515
DoCacheAllKnownLangPrefs()17516 void Document::DoCacheAllKnownLangPrefs() {
17517 MOZ_ASSERT(mMayNeedFontPrefsUpdate);
17518 RefPtr<nsAtom> lang = GetLanguageForStyle();
17519 StaticPresData* data = StaticPresData::Get();
17520 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset.get());
17521 data->GetFontPrefsForLang(nsGkAtoms::x_math);
17522 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12
17523 data->GetFontPrefsForLang(nsGkAtoms::Unicode);
17524 for (const auto& key : mLanguagesUsed) {
17525 data->GetFontPrefsForLang(key);
17526 }
17527 mMayNeedFontPrefsUpdate = false;
17528 }
17529
RecomputeLanguageFromCharset()17530 void Document::RecomputeLanguageFromCharset() {
17531 nsLanguageAtomService* service = nsLanguageAtomService::GetService();
17532 RefPtr<nsAtom> language = service->LookupCharSet(mCharacterSet);
17533 if (language == nsGkAtoms::Unicode) {
17534 language = service->GetLocaleLanguage();
17535 }
17536
17537 if (language == mLanguageFromCharset) {
17538 return;
17539 }
17540
17541 mMayNeedFontPrefsUpdate = true;
17542 mLanguageFromCharset = std::move(language);
17543 }
17544
CookieJarSettings()17545 nsICookieJarSettings* Document::CookieJarSettings() {
17546 // If we are here, this is probably a javascript: URL document. In any case,
17547 // we must have a nsCookieJarSettings. Let's create it.
17548 if (!mCookieJarSettings) {
17549 Document* inProcessParent = GetInProcessParentDocument();
17550
17551 mCookieJarSettings =
17552 inProcessParent
17553 ? net::CookieJarSettings::Create(
17554 inProcessParent->CookieJarSettings()->GetCookieBehavior(),
17555 mozilla::net::CookieJarSettings::Cast(
17556 inProcessParent->CookieJarSettings())
17557 ->GetPartitionKey(),
17558 inProcessParent->CookieJarSettings()
17559 ->GetIsFirstPartyIsolated(),
17560 inProcessParent->CookieJarSettings()
17561 ->GetIsOnContentBlockingAllowList())
17562 : net::CookieJarSettings::Create(NodePrincipal());
17563
17564 if (auto* wgc = GetWindowGlobalChild()) {
17565 net::CookieJarSettingsArgs csArgs;
17566 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs);
17567 // Update cookie settings in the parent process
17568 if (!wgc->SendUpdateCookieJarSettings(csArgs)) {
17569 NS_WARNING(
17570 "Failed to update document's cookie jar settings on the "
17571 "WindowGlobalParent");
17572 }
17573 }
17574 }
17575
17576 return mCookieJarSettings;
17577 }
17578
HasStorageAccessPermissionGranted()17579 bool Document::HasStorageAccessPermissionGranted() {
17580 // The HasStoragePermission flag in LoadInfo remains fixed when
17581 // it is set in the parent process, so we need to check the cache
17582 // to see if the permission is granted afterwards.
17583 nsPIDOMWindowInner* inner = GetInnerWindow();
17584 if (inner && inner->HasStorageAccessPermissionGranted()) {
17585 return true;
17586 }
17587
17588 if (!mChannel) {
17589 return false;
17590 }
17591
17592 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17593 return loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission;
17594 }
17595
HasStorageAccessPermissionGrantedByAllowList()17596 bool Document::HasStorageAccessPermissionGrantedByAllowList() {
17597 // We only care about if the document gets the storage permission via the
17598 // allow list here. So we don't check the storage access cache in the inner
17599 // window.
17600
17601 if (!mChannel) {
17602 return false;
17603 }
17604
17605 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
17606 return loadInfo->GetStoragePermission() ==
17607 nsILoadInfo::StoragePermissionAllowListed;
17608 }
17609
EffectiveStoragePrincipal() const17610 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
17611 nsPIDOMWindowInner* inner = GetInnerWindow();
17612 if (!inner) {
17613 return NodePrincipal();
17614 }
17615
17616 // Return our cached storage principal if one exists.
17617 if (mActiveStoragePrincipal) {
17618 return mActiveStoragePrincipal;
17619 }
17620
17621 // We use the lower-level ContentBlocking API here to ensure this
17622 // check doesn't send notifications.
17623 uint32_t rejectedReason = 0;
17624 if (ContentBlocking::ShouldAllowAccessFor(inner, GetDocumentURI(),
17625 &rejectedReason)) {
17626 return mActiveStoragePrincipal = NodePrincipal();
17627 }
17628
17629 // Let's use the storage principal only if we need to partition the cookie
17630 // jar. When the permission is granted, access will be different and the
17631 // normal principal will be used.
17632 if (ShouldPartitionStorage(rejectedReason) &&
17633 !StoragePartitioningEnabled(
17634 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) {
17635 return mActiveStoragePrincipal = NodePrincipal();
17636 }
17637
17638 return mActiveStoragePrincipal = mPartitionedPrincipal;
17639 }
17640
GetPrincipalForPrefBasedHacks() const17641 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const {
17642 // If the document is sandboxed document or data: document, we should
17643 // get URI of the parent document.
17644 for (const Document* document = this;
17645 document && document->IsContentDocument();
17646 document = document->GetInProcessParentDocument()) {
17647 // The document URI may be about:blank even if it comes from actual web
17648 // site. Therefore, we need to check the URI of its principal.
17649 nsIPrincipal* principal = document->NodePrincipal();
17650 if (principal->GetIsNullPrincipal()) {
17651 continue;
17652 }
17653 return principal;
17654 }
17655 return nullptr;
17656 }
17657
SetIsInitialDocument(bool aIsInitialDocument)17658 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
17659 mIsInitialDocumentInWindow = aIsInitialDocument;
17660
17661 // Asynchronously tell the parent process that we are, or are no longer, the
17662 // initial document. This happens async.
17663 if (auto* wgc = GetWindowGlobalChild()) {
17664 wgc->SendSetIsInitialDocument(aIsInitialDocument);
17665 }
17666 }
17667
17668 // static
AddToplevelLoadingDocument(Document * aDoc)17669 void Document::AddToplevelLoadingDocument(Document* aDoc) {
17670 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
17671 // Currently we're interested in foreground documents only, so bail out early.
17672 if (aDoc->IsInBackgroundWindow() || !XRE_IsContentProcess()) {
17673 return;
17674 }
17675
17676 if (!sLoadingForegroundTopLevelContentDocument) {
17677 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>();
17678 mozilla::ipc::IdleSchedulerChild* idleScheduler =
17679 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
17680 if (idleScheduler) {
17681 idleScheduler->SendRunningPrioritizedOperation();
17682 }
17683 }
17684 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) {
17685 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc);
17686 }
17687 }
17688
17689 // static
RemoveToplevelLoadingDocument(Document * aDoc)17690 void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
17691 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument());
17692 if (sLoadingForegroundTopLevelContentDocument) {
17693 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc);
17694 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) {
17695 delete sLoadingForegroundTopLevelContentDocument;
17696 sLoadingForegroundTopLevelContentDocument = nullptr;
17697
17698 mozilla::ipc::IdleSchedulerChild* idleScheduler =
17699 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
17700 if (idleScheduler) {
17701 idleScheduler->SendPrioritizedOperationDone();
17702 }
17703 }
17704 }
17705 }
17706
DefaultColorScheme() const17707 ColorScheme Document::DefaultColorScheme() const {
17708 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()});
17709 }
17710
PreferredColorScheme(IgnoreRFP aIgnoreRFP) const17711 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
17712 if (aIgnoreRFP == IgnoreRFP::No &&
17713 nsContentUtils::ShouldResistFingerprinting(this)) {
17714 return ColorScheme::Light;
17715 }
17716
17717 if (nsPresContext* pc = GetPresContext()) {
17718 if (auto scheme = pc->GetOverriddenColorScheme()) {
17719 return *scheme;
17720 }
17721 }
17722
17723 // NOTE(emilio): We use IsInChromeDocShell rather than IsChromeDoc
17724 // intentionally, to make chrome documents in content docshells (like about
17725 // pages) use the content color scheme.
17726 if (IsInChromeDocShell()) {
17727 return LookAndFeel::ColorSchemeForChrome();
17728 }
17729 return LookAndFeel::PreferredColorSchemeForContent();
17730 }
17731
HasRecentlyStartedForegroundLoads()17732 bool Document::HasRecentlyStartedForegroundLoads() {
17733 if (!sLoadingForegroundTopLevelContentDocument) {
17734 return false;
17735 }
17736
17737 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length();
17738 ++i) {
17739 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i);
17740 // A page loaded in foreground could be in background now.
17741 if (!doc->IsInBackgroundWindow()) {
17742 nsPIDOMWindowInner* win = doc->GetInnerWindow();
17743 if (win) {
17744 Performance* perf = win->GetPerformance();
17745 if (perf &&
17746 perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
17747 return true;
17748 }
17749 }
17750 }
17751 }
17752
17753 // Didn't find any loading foreground documents, just clear the array.
17754 delete sLoadingForegroundTopLevelContentDocument;
17755 sLoadingForegroundTopLevelContentDocument = nullptr;
17756
17757 mozilla::ipc::IdleSchedulerChild* idleScheduler =
17758 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
17759 if (idleScheduler) {
17760 idleScheduler->SendPrioritizedOperationDone();
17761 }
17762 return false;
17763 }
17764
AddPendingFrameStaticClone(nsFrameLoaderOwner * aElement,nsFrameLoader * aStaticCloneOf)17765 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement,
17766 nsFrameLoader* aStaticCloneOf) {
17767 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement();
17768 clone->mElement = aElement;
17769 clone->mStaticCloneOf = aStaticCloneOf;
17770 }
17771
ShouldAvoidNativeTheme() const17772 bool Document::ShouldAvoidNativeTheme() const {
17773 return StaticPrefs::widget_non_native_theme_enabled() &&
17774 (!IsInChromeDocShell() || XRE_IsContentProcess());
17775 }
17776
UseRegularPrincipal() const17777 bool Document::UseRegularPrincipal() const {
17778 return EffectiveStoragePrincipal() == NodePrincipal();
17779 }
17780
HasThirdPartyChannel()17781 bool Document::HasThirdPartyChannel() {
17782 nsCOMPtr<nsIChannel> channel = GetChannel();
17783 if (channel) {
17784 // We assume that the channel is a third-party by default.
17785 bool thirdParty = true;
17786
17787 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
17788 components::ThirdPartyUtil::Service();
17789 if (!thirdPartyUtil) {
17790 return thirdParty;
17791 }
17792
17793 // Check that if the channel is a third-party to its parent.
17794 nsresult rv =
17795 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty);
17796 if (NS_FAILED(rv)) {
17797 // Assume third-party in case of failure
17798 thirdParty = true;
17799 }
17800
17801 return thirdParty;
17802 }
17803
17804 if (mParentDocument) {
17805 return mParentDocument->HasThirdPartyChannel();
17806 }
17807
17808 return false;
17809 }
17810
ShouldIncludeInTelemetry(bool aAllowExtensionURIs)17811 bool Document::ShouldIncludeInTelemetry(bool aAllowExtensionURIs) {
17812 if (!(IsContentDocument() || IsResourceDoc())) {
17813 return false;
17814 }
17815
17816 if (!aAllowExtensionURIs &&
17817 NodePrincipal()->GetIsAddonOrExpandedAddonPrincipal()) {
17818 return false;
17819 }
17820
17821 return !NodePrincipal()->SchemeIs("about") &&
17822 !NodePrincipal()->SchemeIs("chrome") &&
17823 !NodePrincipal()->SchemeIs("resource");
17824 }
17825
GetConnectedShadowRoots(nsTArray<RefPtr<ShadowRoot>> & aOut) const17826 void Document::GetConnectedShadowRoots(
17827 nsTArray<RefPtr<ShadowRoot>>& aOut) const {
17828 AppendToArray(aOut, mComposedShadowRoots);
17829 }
17830
HasPictureInPictureChildElement() const17831 bool Document::HasPictureInPictureChildElement() const {
17832 return mPictureInPictureChildElementCount > 0;
17833 }
17834
EnableChildElementInPictureInPictureMode()17835 void Document::EnableChildElementInPictureInPictureMode() {
17836 mPictureInPictureChildElementCount++;
17837 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
17838 }
17839
DisableChildElementInPictureInPictureMode()17840 void Document::DisableChildElementInPictureInPictureMode() {
17841 mPictureInPictureChildElementCount--;
17842 MOZ_ASSERT(mPictureInPictureChildElementCount >= 0);
17843 }
17844
AddMediaElementWithMSE()17845 void Document::AddMediaElementWithMSE() {
17846 if (mMediaElementWithMSECount++ == 0) {
17847 WindowGlobalChild* wgc = GetWindowGlobalChild();
17848 if (wgc) {
17849 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
17850 }
17851 }
17852 }
17853
RemoveMediaElementWithMSE()17854 void Document::RemoveMediaElementWithMSE() {
17855 MOZ_ASSERT(mMediaElementWithMSECount > 0);
17856 if (--mMediaElementWithMSECount == 0) {
17857 WindowGlobalChild* wgc = GetWindowGlobalChild();
17858 if (wgc) {
17859 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT);
17860 }
17861 }
17862 }
17863
UnregisterFromMemoryReportingForDataDocument()17864 void Document::UnregisterFromMemoryReportingForDataDocument() {
17865 if (!mAddedToMemoryReportingAsDataDocument) {
17866 return;
17867 }
17868 mAddedToMemoryReportingAsDataDocument = false;
17869 nsIGlobalObject* global = GetScopeObject();
17870 if (global) {
17871 if (nsPIDOMWindowInner* win = global->AsInnerWindow()) {
17872 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting(
17873 this);
17874 }
17875 }
17876 }
OOPChildLoadStarted(BrowserBridgeChild * aChild)17877 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) {
17878 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild));
17879 mOOPChildrenLoading.AppendElement(aChild);
17880 if (mOOPChildrenLoading.Length() == 1) {
17881 // Let's block unload so that we're blocked from going into the BFCache
17882 // until the child has actually notified us that it has done loading.
17883 BlockOnload();
17884 }
17885 }
17886
OOPChildLoadDone(BrowserBridgeChild * aChild)17887 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) {
17888 // aChild will not be in the list if nsDocLoader::Stop() was called, since
17889 // that clears mOOPChildrenLoading. It also dispatches the 'load' event,
17890 // so we don't need to call DocLoaderIsEmpty in that case.
17891 if (mOOPChildrenLoading.RemoveElement(aChild)) {
17892 if (mOOPChildrenLoading.IsEmpty()) {
17893 UnblockOnload(false);
17894 }
17895 RefPtr<nsDocLoader> docLoader(mDocumentContainer);
17896 if (docLoader) {
17897 docLoader->OOPChildrenLoadingIsEmpty();
17898 }
17899 }
17900 }
17901
ClearOOPChildrenLoading()17902 void Document::ClearOOPChildrenLoading() {
17903 nsTArray<const BrowserBridgeChild*> oopChildrenLoading;
17904 mOOPChildrenLoading.SwapElements(oopChildrenLoading);
17905 if (!oopChildrenLoading.IsEmpty()) {
17906 UnblockOnload(false);
17907 }
17908 }
17909
17910 } // namespace mozilla::dom
17911