Document.cpp (730068B)
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 13 #include <inttypes.h> 14 #include <stdlib.h> 15 #include <string.h> 16 17 #include <algorithm> 18 #include <cstddef> 19 #include <cstdint> 20 #include <limits> 21 22 #include "AnchorPositioningUtils.h" 23 #include "Attr.h" 24 #include "ErrorList.h" 25 #include "ExpandedPrincipal.h" 26 #include "MainThreadUtils.h" 27 #include "MobileViewportManager.h" 28 #include "NSSErrorsService.h" 29 #include "NodeUbiReporting.h" 30 #include "NonCustomCSSPropertyId.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/TelemetryTimers.h" 40 #include "js/Value.h" 41 #include "jsapi.h" 42 #include "mozAutoDocUpdate.h" 43 #include "mozIDOMWindow.h" 44 #include "mozIThirdPartyUtil.h" 45 #include "mozilla/AntiTrackingUtils.h" 46 #include "mozilla/ArrayIterator.h" 47 #include "mozilla/AsyncEventDispatcher.h" 48 #include "mozilla/AttributeStyles.h" 49 #include "mozilla/Base64.h" 50 #include "mozilla/BasePrincipal.h" 51 #include "mozilla/BounceTrackingProtection.h" 52 #include "mozilla/CSSEnabledState.h" 53 #include "mozilla/Components.h" 54 #include "mozilla/ContentBlockingAllowList.h" 55 #include "mozilla/ContentBlockingNotifier.h" 56 #include "mozilla/ContentBlockingUserInteraction.h" 57 #include "mozilla/ContentPrincipal.h" 58 #include "mozilla/CycleCollectedJSContext.h" 59 #include "mozilla/DebugOnly.h" 60 #include "mozilla/DocumentStyleRootIterator.h" 61 #include "mozilla/EditorBase.h" 62 #include "mozilla/EditorCommands.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/Likely.h" 77 #include "mozilla/Logging.h" 78 #include "mozilla/LookAndFeel.h" 79 #include "mozilla/MappedDeclarationsBuilder.h" 80 #include "mozilla/Maybe.h" 81 #include "mozilla/MediaFeatureChange.h" 82 #include "mozilla/MediaManager.h" 83 #include "mozilla/MemoryReporting.h" 84 #include "mozilla/NeverDestroyed.h" 85 #include "mozilla/NullPrincipal.h" 86 #include "mozilla/OriginAttributes.h" 87 #include "mozilla/OwningNonNull.h" 88 #include "mozilla/PendingFullscreenEvent.h" 89 #include "mozilla/PermissionDelegateHandler.h" 90 #include "mozilla/PermissionManager.h" 91 #include "mozilla/Preferences.h" 92 #include "mozilla/PreloadHashKey.h" 93 #include "mozilla/PresShell.h" 94 #include "mozilla/PresShellForwards.h" 95 #include "mozilla/PresShellInlines.h" 96 #include "mozilla/ProfilerMarkers.h" 97 #include "mozilla/PseudoStyleType.h" 98 #include "mozilla/RelativeTo.h" 99 #include "mozilla/RestyleManager.h" 100 #include "mozilla/ReverseIterator.h" 101 #include "mozilla/SMILAnimationController.h" 102 #include "mozilla/SMILTimeContainer.h" 103 #include "mozilla/SVGUtils.h" 104 #include "mozilla/SchedulerGroup.h" 105 #include "mozilla/ScopeExit.h" 106 #include "mozilla/ScrollContainerFrame.h" 107 #include "mozilla/ScrollTimelineAnimationTracker.h" 108 #include "mozilla/ServoStyleConsts.h" 109 #include "mozilla/ServoTypes.h" 110 #include "mozilla/SizeOfState.h" 111 #include "mozilla/Sprintf.h" 112 #include "mozilla/StaticAnalysisFunctions.h" 113 #include "mozilla/StaticPrefs_apz.h" 114 #include "mozilla/StaticPrefs_browser.h" 115 #include "mozilla/StaticPrefs_docshell.h" 116 #include "mozilla/StaticPrefs_dom.h" 117 #include "mozilla/StaticPrefs_fission.h" 118 #include "mozilla/StaticPrefs_full_screen_api.h" 119 #include "mozilla/StaticPrefs_layout.h" 120 #include "mozilla/StaticPrefs_network.h" 121 #include "mozilla/StaticPrefs_page_load.h" 122 #include "mozilla/StaticPrefs_privacy.h" 123 #include "mozilla/StaticPrefs_security.h" 124 #include "mozilla/StaticPrefs_widget.h" 125 #include "mozilla/StaticPresData.h" 126 #include "mozilla/StorageAccess.h" 127 #include "mozilla/StoragePrincipalHelper.h" 128 #include "mozilla/StyleSheet.h" 129 #include "mozilla/TelemetryScalarEnums.h" 130 #include "mozilla/TextControlElement.h" 131 #include "mozilla/TextEditor.h" 132 #include "mozilla/URLDecorationStripper.h" 133 #include "mozilla/URLExtraData.h" 134 #include "mozilla/css/ImageLoader.h" 135 #include "mozilla/css/Loader.h" 136 #include "mozilla/css/Rule.h" 137 #include "mozilla/css/SheetParsingMode.h" 138 #include "mozilla/dom/AncestorIterator.h" 139 #include "mozilla/dom/AnonymousContent.h" 140 #include "mozilla/dom/BindContext.h" 141 #include "mozilla/dom/BlobURLProtocolHandler.h" 142 #include "mozilla/dom/BrowserChild.h" 143 #include "mozilla/dom/BrowsingContext.h" 144 #include "mozilla/dom/BrowsingContextGroup.h" 145 #include "mozilla/dom/CDATASection.h" 146 #include "mozilla/dom/CSPDictionariesBinding.h" 147 #include "mozilla/dom/CSSBinding.h" 148 #include "mozilla/dom/CSSCustomPropertyRegisteredEvent.h" 149 #include "mozilla/dom/CanonicalBrowsingContext.h" 150 #include "mozilla/dom/CanvasRenderingContextHelper.h" 151 #include "mozilla/dom/ChromeObserver.h" 152 #include "mozilla/dom/ClientInfo.h" 153 #include "mozilla/dom/ClientState.h" 154 #include "mozilla/dom/CloseWatcherManager.h" 155 #include "mozilla/dom/Comment.h" 156 #include "mozilla/dom/ContentChild.h" 157 #include "mozilla/dom/DOMImplementation.h" 158 #include "mozilla/dom/DOMIntersectionObserver.h" 159 #include "mozilla/dom/DOMStringList.h" 160 #include "mozilla/dom/DocGroup.h" 161 #include "mozilla/dom/DocumentBinding.h" 162 #include "mozilla/dom/DocumentFragment.h" 163 #include "mozilla/dom/DocumentInlines.h" 164 #include "mozilla/dom/DocumentL10n.h" 165 #include "mozilla/dom/DocumentPictureInPicture.h" 166 #include "mozilla/dom/DocumentTimeline.h" 167 #include "mozilla/dom/DocumentType.h" 168 #include "mozilla/dom/ElementBinding.h" 169 #include "mozilla/dom/ErrorEvent.h" 170 #include "mozilla/dom/Event.h" 171 #include "mozilla/dom/EventListenerBinding.h" 172 #include "mozilla/dom/FailedCertSecurityInfoBinding.h" 173 #include "mozilla/dom/FeaturePolicy.h" 174 #include "mozilla/dom/FeaturePolicyUtils.h" 175 #include "mozilla/dom/FontFaceSet.h" 176 #include "mozilla/dom/FragmentDirective.h" 177 #include "mozilla/dom/FromParser.h" 178 #include "mozilla/dom/HTMLAllCollection.h" 179 #include "mozilla/dom/HTMLBodyElement.h" 180 #include "mozilla/dom/HTMLCollectionBinding.h" 181 #include "mozilla/dom/HTMLDialogElement.h" 182 #include "mozilla/dom/HTMLEmbedElement.h" 183 #include "mozilla/dom/HTMLFormElement.h" 184 #include "mozilla/dom/HTMLIFrameElement.h" 185 #include "mozilla/dom/HTMLImageElement.h" 186 #include "mozilla/dom/HTMLInputElement.h" 187 #include "mozilla/dom/HTMLLinkElement.h" 188 #include "mozilla/dom/HTMLMediaElement.h" 189 #include "mozilla/dom/HTMLMetaElement.h" 190 #include "mozilla/dom/HTMLObjectElement.h" 191 #include "mozilla/dom/HTMLSharedElement.h" 192 #include "mozilla/dom/HTMLTextAreaElement.h" 193 #include "mozilla/dom/HighlightRegistry.h" 194 #include "mozilla/dom/InspectorUtils.h" 195 #include "mozilla/dom/IntegrityPolicy.h" 196 #include "mozilla/dom/InteractiveWidget.h" 197 #include "mozilla/dom/Link.h" 198 #include "mozilla/dom/MediaQueryList.h" 199 #include "mozilla/dom/MediaSource.h" 200 #include "mozilla/dom/MutationObservers.h" 201 #include "mozilla/dom/NameSpaceConstants.h" 202 #include "mozilla/dom/NavigationBinding.h" 203 #include "mozilla/dom/Navigator.h" 204 #include "mozilla/dom/NetErrorInfoBinding.h" 205 #include "mozilla/dom/NodeInfo.h" 206 #include "mozilla/dom/NodeIterator.h" 207 #include "mozilla/dom/PContentChild.h" 208 #include "mozilla/dom/PWindowGlobalChild.h" 209 #include "mozilla/dom/PageLoadEventUtils.h" 210 #include "mozilla/dom/PageTransitionEvent.h" 211 #include "mozilla/dom/PageTransitionEventBinding.h" 212 #include "mozilla/dom/Performance.h" 213 #include "mozilla/dom/PermissionMessageUtils.h" 214 #include "mozilla/dom/PolicyContainer.h" 215 #include "mozilla/dom/PopoverData.h" 216 #include "mozilla/dom/PostMessageEvent.h" 217 #include "mozilla/dom/ProcessingInstruction.h" 218 #include "mozilla/dom/Promise.h" 219 #include "mozilla/dom/PromiseNativeHandler.h" 220 #include "mozilla/dom/RemoteBrowser.h" 221 #include "mozilla/dom/ResizeObserver.h" 222 #include "mozilla/dom/RustTypes.h" 223 #include "mozilla/dom/SVGDocument.h" 224 #include "mozilla/dom/SVGElement.h" 225 #include "mozilla/dom/SVGSVGElement.h" 226 #include "mozilla/dom/SVGUseElement.h" 227 #include "mozilla/dom/Sanitizer.h" 228 #include "mozilla/dom/ScriptLoader.h" 229 #include "mozilla/dom/ScriptSettings.h" 230 #include "mozilla/dom/Selection.h" 231 #include "mozilla/dom/ServiceWorkerContainer.h" 232 #include "mozilla/dom/ServiceWorkerDescriptor.h" 233 #include "mozilla/dom/ServiceWorkerManager.h" 234 #include "mozilla/dom/ShadowIncludingTreeIterator.h" 235 #include "mozilla/dom/ShadowRoot.h" 236 #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h" 237 #include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h" 238 #include "mozilla/dom/StyleSheetList.h" 239 #include "mozilla/dom/StyleSheetRemovedEvent.h" 240 #include "mozilla/dom/StyleSheetRemovedEventBinding.h" 241 #include "mozilla/dom/TimeoutManager.h" 242 #include "mozilla/dom/ToggleEvent.h" 243 #include "mozilla/dom/Touch.h" 244 #include "mozilla/dom/TouchEvent.h" 245 #include "mozilla/dom/TreeOrderedArrayInlines.h" 246 #include "mozilla/dom/TreeWalker.h" 247 #include "mozilla/dom/TrustedHTML.h" 248 #include "mozilla/dom/TrustedTypeUtils.h" 249 #include "mozilla/dom/TrustedTypesConstants.h" 250 #include "mozilla/dom/URL.h" 251 #include "mozilla/dom/UseCounterMetrics.h" 252 #include "mozilla/dom/UserActivation.h" 253 #include "mozilla/dom/ViewTransition.h" 254 #include "mozilla/dom/WakeLockJS.h" 255 #include "mozilla/dom/WakeLockSentinel.h" 256 #include "mozilla/dom/WebIdentityHandler.h" 257 #include "mozilla/dom/WindowBinding.h" 258 #include "mozilla/dom/WindowContext.h" 259 #include "mozilla/dom/WindowGlobalChild.h" 260 #include "mozilla/dom/WindowProxyHolder.h" 261 #include "mozilla/dom/WorkerDocumentListener.h" 262 #include "mozilla/dom/XPathEvaluator.h" 263 #include "mozilla/dom/XPathExpression.h" 264 #include "mozilla/dom/XULBroadcastManager.h" 265 #include "mozilla/dom/XULPersist.h" 266 #include "mozilla/dom/fragmentdirectives_ffi_generated.h" 267 #include "mozilla/dom/nsCSPContext.h" 268 #include "mozilla/dom/nsCSPUtils.h" 269 #include "mozilla/dom/nsHTTPSOnlyUtils.h" 270 #include "mozilla/extensions/WebExtensionPolicy.h" 271 #include "mozilla/fallible.h" 272 #include "mozilla/gfx/BaseCoord.h" 273 #include "mozilla/gfx/BaseSize.h" 274 #include "mozilla/gfx/Coord.h" 275 #include "mozilla/gfx/Point.h" 276 #include "mozilla/gfx/ScaleFactor.h" 277 #include "mozilla/glean/DomMetrics.h" 278 #include "mozilla/glean/DomUseCounterMetrics.h" 279 #include "mozilla/intl/EncodingToLang.h" 280 #include "mozilla/intl/LocaleService.h" 281 #include "mozilla/ipc/IdleSchedulerChild.h" 282 #include "mozilla/ipc/MessageChannel.h" 283 #include "mozilla/net/ChannelEventQueue.h" 284 #include "mozilla/net/Cookie.h" 285 #include "mozilla/net/CookieCommons.h" 286 #include "mozilla/net/CookieJarSettings.h" 287 #include "mozilla/net/CookieParser.h" 288 #include "mozilla/net/NeckoChannelParams.h" 289 #include "mozilla/net/RequestContextService.h" 290 #include "nsAboutProtocolUtils.h" 291 #include "nsAtom.h" 292 #include "nsAttrValue.h" 293 #include "nsAttrValueInlines.h" 294 #include "nsBaseHashtable.h" 295 #include "nsBidiUtils.h" 296 #include "nsCRT.h" 297 #include "nsCSSProps.h" 298 #include "nsCSSPseudoElements.h" 299 #include "nsCSSRendering.h" 300 #include "nsCanvasFrame.h" 301 #include "nsCaseTreatment.h" 302 #include "nsCharsetSource.h" 303 #include "nsCommandManager.h" 304 #include "nsCommandParams.h" 305 #include "nsComponentManagerUtils.h" 306 #include "nsContentCreatorFunctions.h" 307 #include "nsContentList.h" 308 #include "nsContentPermissionHelper.h" 309 #include "nsContentSecurityUtils.h" 310 #include "nsContentUtils.h" 311 #include "nsCoord.h" 312 #include "nsCycleCollectionNoteChild.h" 313 #include "nsCycleCollectionTraversalCallback.h" 314 #include "nsDOMAttributeMap.h" 315 #include "nsDOMCaretPosition.h" 316 #include "nsDOMNavigationTiming.h" 317 #include "nsDOMString.h" 318 #include "nsDeviceContext.h" 319 #include "nsDocShell.h" 320 #include "nsDocShellLoadTypes.h" 321 #include "nsError.h" 322 #include "nsEscape.h" 323 #include "nsFocusManager.h" 324 #include "nsFrameLoader.h" 325 #include "nsFrameLoaderOwner.h" 326 #include "nsGenericHTMLElement.h" 327 #include "nsGlobalWindowInner.h" 328 #include "nsGlobalWindowOuter.h" 329 #include "nsHTMLDocument.h" 330 #include "nsHtml5Module.h" 331 #include "nsHtml5Parser.h" 332 #include "nsHtml5TreeOpExecutor.h" 333 #include "nsIAppWindow.h" 334 #include "nsIAsyncShutdown.h" 335 #include "nsIAuthPrompt.h" 336 #include "nsIAuthPrompt2.h" 337 #include "nsIBFCacheEntry.h" 338 #include "nsIBaseWindow.h" 339 #include "nsIBrowserChild.h" 340 #include "nsIBrowserUsage.h" 341 #include "nsICSSLoaderObserver.h" 342 #include "nsICategoryManager.h" 343 #include "nsICertOverrideService.h" 344 #include "nsIClassifiedChannel.h" 345 #include "nsIContent.h" 346 #include "nsIContentInlines.h" 347 #include "nsIContentPolicy.h" 348 #include "nsIContentSecurityPolicy.h" 349 #include "nsIContentSink.h" 350 #include "nsICookieJarSettings.h" 351 #include "nsICookieService.h" 352 #include "nsIDNSService.h" 353 #include "nsIDOMXULCommandDispatcher.h" 354 #include "nsIDocShell.h" 355 #include "nsIDocShellTreeItem.h" 356 #include "nsIDocShellTreeOwner.h" 357 #include "nsIDocumentActivity.h" 358 #include "nsIDocumentEncoder.h" 359 #include "nsIDocumentLoader.h" 360 #include "nsIDocumentLoaderFactory.h" 361 #include "nsIDocumentObserver.h" 362 #include "nsIEditingSession.h" 363 #include "nsIEditor.h" 364 #include "nsIEffectiveTLDService.h" 365 #include "nsIFile.h" 366 #include "nsIFileChannel.h" 367 #include "nsIFrame.h" 368 #include "nsIGlobalObject.h" 369 #include "nsIHTMLCollection.h" 370 #include "nsIHttpChannel.h" 371 #include "nsIHttpChannelInternal.h" 372 #include "nsIIOService.h" 373 #include "nsIImageLoadingContent.h" 374 #include "nsIInlineSpellChecker.h" 375 #include "nsIInputStreamChannel.h" 376 #include "nsIInterfaceRequestorUtils.h" 377 #include "nsILayoutHistoryState.h" 378 #include "nsIMultiPartChannel.h" 379 #include "nsIMutationObserver.h" 380 #include "nsINSSErrorsService.h" 381 #include "nsINamed.h" 382 #include "nsINodeList.h" 383 #include "nsIObjectLoadingContent.h" 384 #include "nsIObserverService.h" 385 #include "nsIParentalControlsService.h" 386 #include "nsIPermission.h" 387 #include "nsIPrompt.h" 388 #include "nsIPropertyBag2.h" 389 #include "nsIPublicKeyPinningService.h" 390 #include "nsIReferrerInfo.h" 391 #include "nsIRefreshURI.h" 392 #include "nsIRequest.h" 393 #include "nsIRequestContext.h" 394 #include "nsIRunnable.h" 395 #include "nsISHEntry.h" 396 #include "nsIScriptElement.h" 397 #include "nsIScriptError.h" 398 #include "nsIScriptGlobalObject.h" 399 #include "nsIScriptSecurityManager.h" 400 #include "nsISecurityConsoleMessage.h" 401 #include "nsISelectionController.h" 402 #include "nsISerialEventTarget.h" 403 #include "nsISimpleEnumerator.h" 404 #include "nsISiteSecurityService.h" 405 #include "nsISocketProvider.h" 406 #include "nsISpeculativeConnect.h" 407 #include "nsIStructuredCloneContainer.h" 408 #include "nsIThread.h" 409 #include "nsITimedChannel.h" 410 #include "nsITimer.h" 411 #include "nsITransportSecurityInfo.h" 412 #include "nsIURIMutator.h" 413 #include "nsIVariant.h" 414 #include "nsIWeakReference.h" 415 #include "nsIWebNavigation.h" 416 #include "nsIWidget.h" 417 #include "nsIX509Cert.h" 418 #include "nsIX509CertValidity.h" 419 #include "nsIXMLContentSink.h" 420 #include "nsIXULRuntime.h" 421 #include "nsImageLoadingContent.h" 422 #include "nsImportModule.h" 423 #include "nsLayoutUtils.h" 424 #include "nsMenuPopupFrame.h" 425 #include "nsMimeTypes.h" 426 #include "nsNetCID.h" 427 #include "nsNetUtil.h" 428 #include "nsNodeInfoManager.h" 429 #include "nsObjectLoadingContent.h" 430 #include "nsPIDOMWindowInlines.h" 431 #include "nsPIWindowRoot.h" 432 #include "nsPoint.h" 433 #include "nsPointerHashKeys.h" 434 #include "nsPresContext.h" 435 #include "nsQueryFrame.h" 436 #include "nsQueryObject.h" 437 #include "nsRange.h" 438 #include "nsRect.h" 439 #include "nsRefreshDriver.h" 440 #include "nsSandboxFlags.h" 441 #include "nsSerializationHelper.h" 442 #include "nsServiceManagerUtils.h" 443 #include "nsStringFlags.h" 444 #include "nsStringIterator.h" 445 #include "nsStyleSheetService.h" 446 #include "nsStyleStruct.h" 447 #include "nsStyleUtil.h" 448 #include "nsSubDocumentFrame.h" 449 #include "nsTextControlFrame.h" 450 #include "nsTextNode.h" 451 #include "nsURLHelper.h" 452 #include "nsUnicharUtils.h" 453 #include "nsWrapperCache.h" 454 #include "nsWrapperCacheInlines.h" 455 #include "nsXPCOMCID.h" 456 #include "nsXULAppAPI.h" 457 #include "nsXULCommandDispatcher.h" 458 #include "nsXULElement.h" 459 #include "nsXULPopupManager.h" 460 #include "nsXULPrototypeDocument.h" 461 #include "prthread.h" 462 #include "prtime.h" 463 #include "prtypes.h" 464 #include "xpcpublic.h" 465 466 // clang-format off 467 #include "mozilla/Encoding.h" 468 #include "encoding_rs.h" 469 // clang-format on 470 471 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0) 472 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1) 473 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2) 474 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3) 475 476 #define NS_MAX_DOCUMENT_WRITE_DEPTH 20 477 478 mozilla::LazyLogModule gPageCacheLog("PageCache"); 479 mozilla::LazyLogModule gSHIPBFCacheLog("SHIPBFCache"); 480 mozilla::LazyLogModule gTimeoutDeferralLog("TimeoutDefer"); 481 mozilla::LazyLogModule gUseCountersLog("UseCounters"); 482 static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection"); 483 484 namespace mozilla { 485 486 using namespace net; 487 488 using performance::pageload_event::PageloadEventType; 489 490 namespace dom { 491 492 class Document::HeaderData { 493 public: 494 HeaderData(nsAtom* aField, const nsAString& aData) 495 : mField(aField), mData(aData) {} 496 497 ~HeaderData() { 498 // Delete iteratively to avoid blowing up the stack, though it shouldn't 499 // happen in practice. 500 UniquePtr<HeaderData> next = std::move(mNext); 501 while (next) { 502 next = std::move(next->mNext); 503 } 504 } 505 506 RefPtr<nsAtom> mField; 507 nsString mData; 508 UniquePtr<HeaderData> mNext; 509 }; 510 511 AutoTArray<Document*, 8>* Document::sLoadingForegroundTopLevelContentDocument = 512 nullptr; 513 514 static LinkedList<Document>& AllDocumentsList() { 515 static NeverDestroyed<LinkedList<Document>> sAllDocuments; 516 return *sAllDocuments; 517 } 518 519 static LazyLogModule gDocumentLeakPRLog("DocumentLeak"); 520 static LazyLogModule gCspPRLog("CSP"); 521 LazyLogModule gUserInteractionPRLog("UserInteraction"); 522 523 static nsresult GetHttpChannelHelper(nsIChannel* aChannel, 524 nsIHttpChannel** aHttpChannel) { 525 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); 526 if (httpChannel) { 527 httpChannel.forget(aHttpChannel); 528 return NS_OK; 529 } 530 531 nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel); 532 if (!multipart) { 533 *aHttpChannel = nullptr; 534 return NS_OK; 535 } 536 537 nsCOMPtr<nsIChannel> baseChannel; 538 nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel)); 539 if (NS_WARN_IF(NS_FAILED(rv))) { 540 return rv; 541 } 542 543 httpChannel = do_QueryInterface(baseChannel); 544 httpChannel.forget(aHttpChannel); 545 546 return NS_OK; 547 } 548 549 } // namespace dom 550 551 #define NAME_NOT_VALID ((nsSimpleContentList*)1) 552 553 IdentifierMapEntry::IdentifierMapEntry( 554 const IdentifierMapEntry::DependentAtomOrString* aKey) 555 : mKey(aKey ? *aKey : nullptr) {} 556 557 void IdentifierMapEntry::Traverse( 558 nsCycleCollectionTraversalCallback* aCallback) { 559 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, 560 "mIdentifierMap mNameContentList"); 561 aCallback->NoteXPCOMChild(static_cast<nsINodeList*>(mNameContentList)); 562 563 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, 564 "mIdentifierMap mDocumentNameContentList"); 565 aCallback->NoteXPCOMChild( 566 static_cast<nsINodeList*>(mDocumentNameContentList)); 567 568 if (mImageElement) { 569 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, 570 "mIdentifierMap mImageElement element"); 571 nsIContent* imageElement = mImageElement; 572 aCallback->NoteXPCOMChild(imageElement); 573 } 574 } 575 576 bool IdentifierMapEntry::IsEmpty() { 577 return mIdContentList.IsEmpty() && !mNameContentList && 578 !mDocumentNameContentList && !mChangeCallbacks && !mImageElement; 579 } 580 581 bool IdentifierMapEntry::HasNameElement() const { 582 return mNameContentList && mNameContentList->Length() != 0; 583 } 584 585 void IdentifierMapEntry::AddContentChangeCallback( 586 Document::IDTargetObserver aCallback, void* aData, bool aForImage) { 587 if (!mChangeCallbacks) { 588 mChangeCallbacks = MakeUnique<nsTHashtable<ChangeCallbackEntry>>(); 589 } 590 591 ChangeCallback cc = {aCallback, aData, aForImage}; 592 mChangeCallbacks->PutEntry(cc); 593 } 594 595 void IdentifierMapEntry::RemoveContentChangeCallback( 596 Document::IDTargetObserver aCallback, void* aData, bool aForImage) { 597 if (!mChangeCallbacks) return; 598 ChangeCallback cc = {aCallback, aData, aForImage}; 599 mChangeCallbacks->RemoveEntry(cc); 600 if (mChangeCallbacks->Count() == 0) { 601 mChangeCallbacks = nullptr; 602 } 603 } 604 605 void IdentifierMapEntry::FireChangeCallbacks(Element* aOldElement, 606 Element* aNewElement, 607 bool aImageOnly) { 608 if (!mChangeCallbacks) return; 609 610 for (auto iter = mChangeCallbacks->Iter(); !iter.Done(); iter.Next()) { 611 IdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get(); 612 // Don't fire image changes for non-image observers, and don't fire element 613 // changes for image observers when an image override is active. 614 if (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) { 615 continue; 616 } 617 618 if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) { 619 iter.Remove(); 620 } 621 } 622 } 623 624 void IdentifierMapEntry::AddIdElement(Element* aElement) { 625 MOZ_ASSERT(aElement, "Must have element"); 626 MOZ_ASSERT(!mIdContentList.Contains(nullptr), "Why is null in our list?"); 627 628 size_t index = mIdContentList.Insert(*aElement); 629 if (index == 0) { 630 Element* oldElement = mIdContentList.SafeElementAt(1, nullptr); 631 FireChangeCallbacks(oldElement, aElement); 632 } 633 } 634 635 void IdentifierMapEntry::RemoveIdElement(Element* aElement) { 636 MOZ_ASSERT(aElement, "Missing element"); 637 638 // This should only be called while the document is in an update. 639 // Assertions near the call to this method guarantee this. 640 641 // This could fire in OOM situations 642 // Only assert this in HTML documents for now as XUL does all sorts of weird 643 // crap. 644 NS_ASSERTION(!aElement->OwnerDoc()->IsHTMLDocument() || 645 mIdContentList.Contains(aElement), 646 "Removing id entry that doesn't exist"); 647 648 // XXXbz should this ever Compact() I guess when all the content is gone 649 // we'll just get cleaned up in the natural order of things... 650 Element* currentElement = mIdContentList.SafeElementAt(0, nullptr); 651 mIdContentList.RemoveElement(*aElement); 652 if (currentElement == aElement) { 653 FireChangeCallbacks(currentElement, 654 mIdContentList.SafeElementAt(0, nullptr)); 655 } 656 } 657 658 void IdentifierMapEntry::SetImageElement(Element* aElement) { 659 Element* oldElement = GetImageIdElement(); 660 mImageElement = aElement; 661 Element* newElement = GetImageIdElement(); 662 if (oldElement != newElement) { 663 FireChangeCallbacks(oldElement, newElement, true); 664 } 665 } 666 667 void IdentifierMapEntry::ClearAndNotify() { 668 Element* currentElement = mIdContentList.SafeElementAt(0, nullptr); 669 mIdContentList.Clear(); 670 if (currentElement) { 671 FireChangeCallbacks(currentElement, nullptr); 672 } 673 mNameContentList = nullptr; 674 mDocumentNameContentList = nullptr; 675 if (mImageElement) { 676 SetImageElement(nullptr); 677 } 678 mChangeCallbacks = nullptr; 679 } 680 681 namespace dom { 682 683 class SimpleHTMLCollection final : public nsSimpleContentList, 684 public nsIHTMLCollection { 685 public: 686 explicit SimpleHTMLCollection(nsINode* aRoot) : nsSimpleContentList(aRoot) {} 687 688 NS_DECL_ISUPPORTS_INHERITED 689 690 virtual nsINode* GetParentObject() override { 691 return nsSimpleContentList::GetParentObject(); 692 } 693 virtual uint32_t Length() override { return nsSimpleContentList::Length(); } 694 virtual Element* GetElementAt(uint32_t aIndex) override { 695 return mElements.SafeElementAt(aIndex)->AsElement(); 696 } 697 698 virtual Element* GetFirstNamedElement(const nsAString& aName, 699 bool& aFound) override { 700 aFound = false; 701 RefPtr<nsAtom> name = NS_Atomize(aName); 702 for (uint32_t i = 0; i < mElements.Length(); i++) { 703 MOZ_DIAGNOSTIC_ASSERT(mElements[i]); 704 Element* element = mElements[i]->AsElement(); 705 if (element->GetID() == name || 706 (element->HasName() && 707 element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue() == name)) { 708 aFound = true; 709 return element; 710 } 711 } 712 return nullptr; 713 } 714 715 virtual void GetSupportedNames(nsTArray<nsString>& aNames) override { 716 AutoTArray<nsAtom*, 8> atoms; 717 for (uint32_t i = 0; i < mElements.Length(); i++) { 718 MOZ_DIAGNOSTIC_ASSERT(mElements[i]); 719 Element* element = mElements[i]->AsElement(); 720 721 nsAtom* id = element->GetID(); 722 MOZ_ASSERT(id != nsGkAtoms::_empty); 723 if (id && !atoms.Contains(id)) { 724 atoms.AppendElement(id); 725 } 726 727 if (element->HasName()) { 728 nsAtom* name = element->GetParsedAttr(nsGkAtoms::name)->GetAtomValue(); 729 MOZ_ASSERT(name && name != nsGkAtoms::_empty); 730 if (name && !atoms.Contains(name)) { 731 atoms.AppendElement(name); 732 } 733 } 734 } 735 736 nsString* names = aNames.AppendElements(atoms.Length()); 737 for (uint32_t i = 0; i < atoms.Length(); i++) { 738 atoms[i]->ToString(names[i]); 739 } 740 } 741 742 virtual JSObject* GetWrapperPreserveColorInternal() override { 743 return nsWrapperCache::GetWrapperPreserveColor(); 744 } 745 virtual void PreserveWrapperInternal( 746 nsISupports* aScriptObjectHolder) override { 747 nsWrapperCache::PreserveWrapper(aScriptObjectHolder); 748 } 749 virtual JSObject* WrapObject(JSContext* aCx, 750 JS::Handle<JSObject*> aGivenProto) override { 751 return HTMLCollection_Binding::Wrap(aCx, this, aGivenProto); 752 } 753 754 using nsBaseContentList::Item; 755 756 private: 757 virtual ~SimpleHTMLCollection() = default; 758 }; 759 760 NS_IMPL_ISUPPORTS_INHERITED(SimpleHTMLCollection, nsSimpleContentList, 761 nsIHTMLCollection) 762 763 } // namespace dom 764 765 void IdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) { 766 if (!mNameContentList) { 767 mNameContentList = new dom::SimpleHTMLCollection(aNode); 768 } 769 770 mNameContentList->AppendElement(aElement); 771 } 772 773 void IdentifierMapEntry::RemoveNameElement(Element* aElement) { 774 if (mNameContentList) { 775 mNameContentList->RemoveElement(aElement); 776 } 777 } 778 779 void IdentifierMapEntry::AddDocumentNameElement( 780 Document* aDocument, nsGenericHTMLElement* aElement) { 781 if (!mDocumentNameContentList) { 782 mDocumentNameContentList = new dom::SimpleHTMLCollection(aDocument); 783 } 784 785 mDocumentNameContentList->AppendElement(aElement); 786 } 787 788 void IdentifierMapEntry::RemoveDocumentNameElement( 789 nsGenericHTMLElement* aElement) { 790 if (mDocumentNameContentList) { 791 mDocumentNameContentList->RemoveElement(aElement); 792 } 793 } 794 795 bool IdentifierMapEntry::HasDocumentNameElement() const { 796 return mDocumentNameContentList && mDocumentNameContentList->Length() != 0; 797 } 798 799 bool IdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() const { 800 Element* idElement = GetIdElement(); 801 return idElement && 802 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement); 803 } 804 805 size_t IdentifierMapEntry::SizeOfExcludingThis( 806 MallocSizeOf aMallocSizeOf) const { 807 return mKey.mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf); 808 } 809 810 // Helper structs for the content->subdoc map 811 812 class SubDocMapEntry : public PLDHashEntryHdr { 813 public: 814 // Both of these are strong references 815 dom::Element* mKey; // must be first, to look like PLDHashEntryStub 816 dom::Document* mSubDocument; 817 }; 818 819 class OnloadBlocker final : public nsIRequest { 820 public: 821 OnloadBlocker() = default; 822 823 NS_DECL_ISUPPORTS 824 NS_DECL_NSIREQUEST 825 826 private: 827 ~OnloadBlocker() = default; 828 }; 829 830 NS_IMPL_ISUPPORTS(OnloadBlocker, nsIRequest) 831 832 NS_IMETHODIMP 833 OnloadBlocker::GetName(nsACString& aResult) { 834 aResult.AssignLiteral("about:document-onload-blocker"); 835 return NS_OK; 836 } 837 838 NS_IMETHODIMP 839 OnloadBlocker::IsPending(bool* _retval) { 840 *_retval = true; 841 return NS_OK; 842 } 843 844 NS_IMETHODIMP 845 OnloadBlocker::GetStatus(nsresult* status) { 846 *status = NS_OK; 847 return NS_OK; 848 } 849 850 NS_IMETHODIMP OnloadBlocker::SetCanceledReason(const nsACString& aReason) { 851 return SetCanceledReasonImpl(aReason); 852 } 853 854 NS_IMETHODIMP OnloadBlocker::GetCanceledReason(nsACString& aReason) { 855 return GetCanceledReasonImpl(aReason); 856 } 857 858 NS_IMETHODIMP OnloadBlocker::CancelWithReason(nsresult aStatus, 859 const nsACString& aReason) { 860 return CancelWithReasonImpl(aStatus, aReason); 861 } 862 NS_IMETHODIMP 863 OnloadBlocker::Cancel(nsresult status) { return NS_OK; } 864 NS_IMETHODIMP 865 OnloadBlocker::Suspend(void) { return NS_OK; } 866 NS_IMETHODIMP 867 OnloadBlocker::Resume(void) { return NS_OK; } 868 869 NS_IMETHODIMP 870 OnloadBlocker::GetLoadGroup(nsILoadGroup** aLoadGroup) { 871 *aLoadGroup = nullptr; 872 return NS_OK; 873 } 874 875 NS_IMETHODIMP 876 OnloadBlocker::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; } 877 878 NS_IMETHODIMP 879 OnloadBlocker::GetLoadFlags(nsLoadFlags* aLoadFlags) { 880 *aLoadFlags = nsIRequest::LOAD_NORMAL; 881 return NS_OK; 882 } 883 884 NS_IMETHODIMP 885 OnloadBlocker::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { 886 return GetTRRModeImpl(aTRRMode); 887 } 888 889 NS_IMETHODIMP 890 OnloadBlocker::SetTRRMode(nsIRequest::TRRMode aTRRMode) { 891 return SetTRRModeImpl(aTRRMode); 892 } 893 894 NS_IMETHODIMP 895 OnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } 896 897 // ================================================================== 898 899 namespace dom { 900 901 ExternalResourceMap::ExternalResourceMap() : mHaveShutDown(false) {} 902 903 Document* ExternalResourceMap::RequestResource( 904 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode, 905 Document* aDisplayDocument, ExternalResourceLoad** aPendingLoad) { 906 // If we ever start allowing non-same-origin loads here, we might need to do 907 // something interesting with aRequestingPrincipal even for the hashtable 908 // gets. 909 MOZ_ASSERT(aURI, "Must have a URI"); 910 MOZ_ASSERT(aRequestingNode, "Must have a node"); 911 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo"); 912 *aPendingLoad = nullptr; 913 if (mHaveShutDown) { 914 return nullptr; 915 } 916 917 // First, make sure we strip the ref from aURI. 918 nsCOMPtr<nsIURI> clone; 919 nsresult rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(clone)); 920 if (NS_FAILED(rv) || !clone) { 921 return nullptr; 922 } 923 924 ExternalResource* resource; 925 mMap.Get(clone, &resource); 926 if (resource) { 927 return resource->mDocument; 928 } 929 930 bool loadStartSucceeded = 931 mPendingLoads.WithEntryHandle(clone, [&](auto&& loadEntry) { 932 if (!loadEntry) { 933 loadEntry.Insert(MakeRefPtr<PendingLoad>(aDisplayDocument)); 934 935 if (NS_FAILED(loadEntry.Data()->StartLoad(clone, aReferrerInfo, 936 aRequestingNode))) { 937 return false; 938 } 939 } 940 941 RefPtr<PendingLoad> load(loadEntry.Data()); 942 load.forget(aPendingLoad); 943 return true; 944 }); 945 if (!loadStartSucceeded) { 946 // Make sure we don't thrash things by trying this load again, since 947 // chances are it failed for good reasons (security check, etc). 948 // This must be done outside the WithEntryHandle functor, as it accesses 949 // mPendingLoads. 950 AddExternalResource(clone, nullptr, nullptr, aDisplayDocument); 951 } 952 953 return nullptr; 954 } 955 956 void ExternalResourceMap::EnumerateResources(SubDocEnumFunc aCallback) const { 957 nsTArray<RefPtr<Document>> docs(mMap.Count()); 958 for (const auto& entry : mMap.Values()) { 959 if (Document* doc = entry->mDocument) { 960 docs.AppendElement(doc); 961 } 962 } 963 for (auto& doc : docs) { 964 if (aCallback(*doc) == CallState::Stop) { 965 return; 966 } 967 } 968 } 969 970 void ExternalResourceMap::CollectDescendantDocuments( 971 nsTArray<RefPtr<Document>>& aDocs, SubDocTestFunc aCallback) const { 972 for (const auto& entry : mMap.Values()) { 973 if (Document* doc = entry->mDocument) { 974 if (aCallback(doc)) { 975 aDocs.AppendElement(doc); 976 } 977 doc->CollectDescendantDocuments(aDocs, Document::IncludeSubResources::Yes, 978 aCallback); 979 } 980 } 981 } 982 983 void ExternalResourceMap::Traverse( 984 nsCycleCollectionTraversalCallback* aCallback) const { 985 // mPendingLoads will get cleared out as the requests complete, so 986 // no need to worry about those here. 987 for (const auto& entry : mMap) { 988 ExternalResourceMap::ExternalResource* resource = entry.GetWeak(); 989 990 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, 991 "mExternalResourceMap.mMap entry" 992 "->mDocument"); 993 aCallback->NoteXPCOMChild(ToSupports(resource->mDocument)); 994 995 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, 996 "mExternalResourceMap.mMap entry" 997 "->mViewer"); 998 aCallback->NoteXPCOMChild(resource->mViewer); 999 1000 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, 1001 "mExternalResourceMap.mMap entry" 1002 "->mLoadGroup"); 1003 aCallback->NoteXPCOMChild(resource->mLoadGroup); 1004 } 1005 } 1006 1007 void ExternalResourceMap::HideViewers() { 1008 for (const auto& entry : mMap) { 1009 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer; 1010 if (viewer) { 1011 viewer->Hide(); 1012 } 1013 } 1014 } 1015 1016 void ExternalResourceMap::ShowViewers() { 1017 for (const auto& entry : mMap) { 1018 nsCOMPtr<nsIDocumentViewer> viewer = entry.GetData()->mViewer; 1019 if (viewer) { 1020 viewer->Show(); 1021 } 1022 } 1023 } 1024 1025 void TransferShowingState(Document* aFromDoc, Document* aToDoc) { 1026 MOZ_ASSERT(aFromDoc && aToDoc, "transferring showing state from/to null doc"); 1027 1028 if (aFromDoc->IsShowing()) { 1029 aToDoc->OnPageShow(true, nullptr); 1030 } 1031 } 1032 1033 nsresult ExternalResourceMap::AddExternalResource(nsIURI* aURI, 1034 nsIDocumentViewer* aViewer, 1035 nsILoadGroup* aLoadGroup, 1036 Document* aDisplayDocument) { 1037 MOZ_ASSERT(aURI, "Unexpected call"); 1038 MOZ_ASSERT((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup), 1039 "Must have both or neither"); 1040 1041 RefPtr<PendingLoad> load; 1042 mPendingLoads.Remove(aURI, getter_AddRefs(load)); 1043 1044 nsresult rv = NS_OK; 1045 1046 nsCOMPtr<Document> doc; 1047 if (aViewer) { 1048 doc = aViewer->GetDocument(); 1049 NS_ASSERTION(doc, "Must have a document"); 1050 1051 doc->SetDisplayDocument(aDisplayDocument); 1052 1053 // Make sure that hiding our viewer will tear down its presentation. 1054 aViewer->SetSticky(false); 1055 1056 rv = aViewer->Init(nullptr, LayoutDeviceIntRect(), nullptr); 1057 if (NS_SUCCEEDED(rv)) { 1058 rv = aViewer->Open(nullptr, nullptr); 1059 } 1060 1061 if (NS_FAILED(rv)) { 1062 doc = nullptr; 1063 aViewer = nullptr; 1064 aLoadGroup = nullptr; 1065 } 1066 } 1067 1068 ExternalResource* newResource = 1069 mMap.InsertOrUpdate(aURI, MakeUnique<ExternalResource>()).get(); 1070 1071 newResource->mDocument = doc; 1072 newResource->mViewer = aViewer; 1073 newResource->mLoadGroup = aLoadGroup; 1074 if (doc) { 1075 if (nsPresContext* pc = doc->GetPresContext()) { 1076 pc->RecomputeBrowsingContextDependentData(); 1077 } 1078 TransferShowingState(aDisplayDocument, doc); 1079 } 1080 1081 const nsTArray<nsCOMPtr<nsIObserver>>& obs = load->Observers(); 1082 for (uint32_t i = 0; i < obs.Length(); ++i) { 1083 obs[i]->Observe(ToSupports(doc), "external-resource-document-created", 1084 nullptr); 1085 } 1086 1087 return rv; 1088 } 1089 1090 NS_IMPL_ISUPPORTS(ExternalResourceMap::PendingLoad, nsIStreamListener, 1091 nsIRequestObserver) 1092 1093 NS_IMETHODIMP 1094 ExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest* aRequest) { 1095 ExternalResourceMap& map = mDisplayDocument->ExternalResourceMap(); 1096 if (map.HaveShutDown()) { 1097 return NS_BINDING_ABORTED; 1098 } 1099 1100 nsCOMPtr<nsIDocumentViewer> viewer; 1101 nsCOMPtr<nsILoadGroup> loadGroup; 1102 nsresult rv = 1103 SetupViewer(aRequest, getter_AddRefs(viewer), getter_AddRefs(loadGroup)); 1104 1105 // Make sure to do this no matter what 1106 nsresult rv2 = 1107 map.AddExternalResource(mURI, viewer, loadGroup, mDisplayDocument); 1108 if (NS_FAILED(rv)) { 1109 return rv; 1110 } 1111 if (NS_FAILED(rv2)) { 1112 mTargetListener = nullptr; 1113 return rv2; 1114 } 1115 1116 return mTargetListener->OnStartRequest(aRequest); 1117 } 1118 1119 nsresult ExternalResourceMap::PendingLoad::SetupViewer( 1120 nsIRequest* aRequest, nsIDocumentViewer** aViewer, 1121 nsILoadGroup** aLoadGroup) { 1122 MOZ_ASSERT(!mTargetListener, "Unexpected call to OnStartRequest"); 1123 *aViewer = nullptr; 1124 *aLoadGroup = nullptr; 1125 1126 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); 1127 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); 1128 1129 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest)); 1130 if (httpChannel) { 1131 bool requestSucceeded; 1132 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) || 1133 !requestSucceeded) { 1134 // Bail out on this load, since it looks like we have an HTTP error page 1135 return NS_BINDING_ABORTED; 1136 } 1137 } 1138 1139 nsAutoCString type; 1140 chan->GetContentType(type); 1141 1142 nsCOMPtr<nsILoadGroup> loadGroup; 1143 chan->GetLoadGroup(getter_AddRefs(loadGroup)); 1144 1145 // Give this document its own loadgroup 1146 nsCOMPtr<nsILoadGroup> newLoadGroup = 1147 do_CreateInstance(NS_LOADGROUP_CONTRACTID); 1148 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY); 1149 newLoadGroup->SetLoadGroup(loadGroup); 1150 1151 nsCOMPtr<nsIInterfaceRequestor> callbacks; 1152 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); 1153 1154 nsCOMPtr<nsIInterfaceRequestor> newCallbacks = 1155 new LoadgroupCallbacks(callbacks); 1156 newLoadGroup->SetNotificationCallbacks(newCallbacks); 1157 1158 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = 1159 nsContentUtils::FindInternalDocumentViewer(type); 1160 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE); 1161 1162 nsCOMPtr<nsIDocumentViewer> viewer; 1163 nsCOMPtr<nsIStreamListener> listener; 1164 nsresult rv = docLoaderFactory->CreateInstance( 1165 "external-resource", chan, newLoadGroup, type, nullptr, nullptr, 1166 getter_AddRefs(listener), getter_AddRefs(viewer)); 1167 NS_ENSURE_SUCCESS(rv, rv); 1168 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED); 1169 1170 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener); 1171 if (!parser) { 1172 /// We don't want to deal with the various fake documents yet 1173 return NS_ERROR_NOT_IMPLEMENTED; 1174 } 1175 1176 // We can't handle HTML and other weird things here yet. 1177 nsIContentSink* sink = parser->GetContentSink(); 1178 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink); 1179 if (!xmlSink) { 1180 return NS_ERROR_NOT_IMPLEMENTED; 1181 } 1182 1183 listener.swap(mTargetListener); 1184 viewer.forget(aViewer); 1185 newLoadGroup.forget(aLoadGroup); 1186 return NS_OK; 1187 } 1188 1189 NS_IMETHODIMP 1190 ExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest, 1191 nsIInputStream* aStream, 1192 uint64_t aOffset, 1193 uint32_t aCount) { 1194 // mTargetListener might be null if SetupViewer or AddExternalResource failed. 1195 NS_ENSURE_TRUE(mTargetListener, NS_ERROR_FAILURE); 1196 if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) { 1197 return NS_BINDING_ABORTED; 1198 } 1199 return mTargetListener->OnDataAvailable(aRequest, aStream, aOffset, aCount); 1200 } 1201 1202 NS_IMETHODIMP 1203 ExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest, 1204 nsresult aStatus) { 1205 // mTargetListener might be null if SetupViewer or AddExternalResource failed 1206 if (mTargetListener) { 1207 nsCOMPtr<nsIStreamListener> listener; 1208 mTargetListener.swap(listener); 1209 return listener->OnStopRequest(aRequest, aStatus); 1210 } 1211 1212 return NS_OK; 1213 } 1214 1215 nsresult ExternalResourceMap::PendingLoad::StartLoad( 1216 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode) { 1217 MOZ_ASSERT(aURI, "Must have a URI"); 1218 MOZ_ASSERT(aRequestingNode, "Must have a node"); 1219 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo"); 1220 1221 nsCOMPtr<nsILoadGroup> loadGroup = 1222 aRequestingNode->OwnerDoc()->GetDocumentLoadGroup(); 1223 1224 nsresult rv = NS_OK; 1225 nsCOMPtr<nsIChannel> channel; 1226 rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode, 1227 nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT, 1228 nsIContentPolicy::TYPE_INTERNAL_EXTERNAL_RESOURCE, 1229 nullptr, // aPerformanceStorage 1230 loadGroup); 1231 NS_ENSURE_SUCCESS(rv, rv); 1232 1233 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); 1234 if (httpChannel) { 1235 rv = httpChannel->SetReferrerInfo(aReferrerInfo); 1236 (void)NS_WARN_IF(NS_FAILED(rv)); 1237 } 1238 1239 mURI = aURI; 1240 1241 return channel->AsyncOpen(this); 1242 } 1243 1244 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks, 1245 nsIInterfaceRequestor) 1246 1247 #define IMPL_SHIM(_i) \ 1248 NS_IMPL_ISUPPORTS(ExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i) 1249 1250 IMPL_SHIM(nsILoadContext) 1251 IMPL_SHIM(nsIProgressEventSink) 1252 IMPL_SHIM(nsIChannelEventSink) 1253 1254 #undef IMPL_SHIM 1255 1256 #define IID_IS(_i) aIID.Equals(NS_GET_IID(_i)) 1257 1258 #define TRY_SHIM(_i) \ 1259 PR_BEGIN_MACRO \ 1260 if (IID_IS(_i)) { \ 1261 nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \ 1262 if (!real) { \ 1263 return NS_NOINTERFACE; \ 1264 } \ 1265 nsCOMPtr<_i> shim = new _i##Shim(this, real); \ 1266 shim.forget(aSink); \ 1267 return NS_OK; \ 1268 } \ 1269 PR_END_MACRO 1270 1271 NS_IMETHODIMP 1272 ExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID& aIID, 1273 void** aSink) { 1274 if (mCallbacks && (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) || 1275 IID_IS(nsIAuthPrompt2) || IID_IS(nsIBrowserChild))) { 1276 return mCallbacks->GetInterface(aIID, aSink); 1277 } 1278 1279 *aSink = nullptr; 1280 1281 TRY_SHIM(nsILoadContext); 1282 TRY_SHIM(nsIProgressEventSink); 1283 TRY_SHIM(nsIChannelEventSink); 1284 1285 return NS_NOINTERFACE; 1286 } 1287 1288 #undef TRY_SHIM 1289 #undef IID_IS 1290 1291 ExternalResourceMap::ExternalResource::~ExternalResource() { 1292 if (mViewer) { 1293 mViewer->Close(nullptr); 1294 mViewer->Destroy(); 1295 } 1296 } 1297 1298 // ================================================================== 1299 // = 1300 // ================================================================== 1301 1302 // If we ever have an nsIDocumentObserver notification for stylesheet title 1303 // changes we should update the list from that instead of overriding 1304 // EnsureFresh. 1305 class DOMStyleSheetSetList final : public DOMStringList { 1306 public: 1307 explicit DOMStyleSheetSetList(Document* aDocument); 1308 1309 void Disconnect() { mDocument = nullptr; } 1310 1311 virtual void EnsureFresh() override; 1312 1313 protected: 1314 Document* mDocument; // Our document; weak ref. It'll let us know if it 1315 // dies. 1316 }; 1317 1318 DOMStyleSheetSetList::DOMStyleSheetSetList(Document* aDocument) 1319 : mDocument(aDocument) { 1320 NS_ASSERTION(mDocument, "Must have document!"); 1321 } 1322 1323 void DOMStyleSheetSetList::EnsureFresh() { 1324 MOZ_ASSERT(NS_IsMainThread()); 1325 1326 mNames.Clear(); 1327 1328 if (!mDocument) { 1329 return; // Spec says "no exceptions", and we have no style sets if we have 1330 // no document, for sure 1331 } 1332 1333 size_t count = mDocument->SheetCount(); 1334 nsAutoString title; 1335 for (size_t index = 0; index < count; index++) { 1336 StyleSheet* sheet = mDocument->SheetAt(index); 1337 NS_ASSERTION(sheet, "Null sheet in sheet list!"); 1338 sheet->GetTitle(title); 1339 if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) { 1340 return; 1341 } 1342 } 1343 } 1344 1345 Document::PendingFrameStaticClone::~PendingFrameStaticClone() = default; 1346 1347 // ================================================================== 1348 // = 1349 // ================================================================== 1350 1351 Document::InternalCommandDataHashtable* 1352 Document::sInternalCommandDataHashtable = nullptr; 1353 1354 // static 1355 void Document::Shutdown() { 1356 if (sInternalCommandDataHashtable) { 1357 sInternalCommandDataHashtable->Clear(); 1358 delete sInternalCommandDataHashtable; 1359 sInternalCommandDataHashtable = nullptr; 1360 } 1361 } 1362 1363 Document::Document(const char* aContentType, 1364 mozilla::dom::LoadedAsData aLoadedAsData) 1365 : nsINode(nullptr), 1366 DocumentOrShadowRoot(this), 1367 mCharacterSet(WINDOWS_1252_ENCODING), 1368 mCharacterSetSource(0), 1369 mParentDocument(nullptr), 1370 mCachedRootElement(nullptr), 1371 mNodeInfoManager(nullptr), 1372 #ifdef DEBUG 1373 mStyledLinksCleared(false), 1374 #endif 1375 mInitialStatus(Document::InitialStatus::NeverInitial), 1376 mCachedStateObjectValid(false), 1377 mBlockAllMixedContent(false), 1378 mBlockAllMixedContentPreloads(false), 1379 mUpgradeInsecureRequests(false), 1380 mUpgradeInsecurePreloads(false), 1381 mDevToolsWatchingDOMMutations(false), 1382 mLoadedAsData(aLoadedAsData == LoadedAsData::AsData), 1383 mRenderingSuppressedForViewTransitions(false), 1384 mBidiEnabled(false), 1385 mMayNeedFontPrefsUpdate(true), 1386 mInitialAboutBlankLoadCompleting(false), 1387 mIgnoreDocGroupMismatches(false), 1388 mAddedToMemoryReportingAsDataDocument(false), 1389 mMayStartLayout(true), 1390 mHaveFiredTitleChange(false), 1391 mIsShowing(false), 1392 mVisible(true), 1393 mIsCompletelyLoaded(false), 1394 mRemovedFromDocShell(false), 1395 // mAllowDNSPrefetch starts true, so that we can always reliably && it 1396 // with various values that might disable it. Since we never prefetch 1397 // unless we get a window, and in that case the docshell value will get 1398 // &&-ed in, this is safe. 1399 mAllowDNSPrefetch(true), 1400 mIsStaticDocument(false), 1401 mCreatingStaticClone(false), 1402 mHasPrintCallbacks(false), 1403 mInUnlinkOrDeletion(false), 1404 mHasHadScriptHandlingObject(false), 1405 mIsBeingUsedAsImage(false), 1406 mChromeRulesEnabled(false), 1407 mInChromeDocShell(false), 1408 mIsSyntheticDocument(false), 1409 mHasLinksToUpdateRunnable(false), 1410 mFlushingPendingLinkUpdates(false), 1411 mMayHaveDOMMutationObservers(false), 1412 mMayHaveAnimationObservers(false), 1413 mHasCSPDeliveredThroughHeader(false), 1414 mBFCacheDisallowed(false), 1415 mHasHadDefaultView(false), 1416 mStyleSheetChangeEventsEnabled(false), 1417 mDevToolsAnonymousAndShadowEventsEnabled(false), 1418 mPausedByDevTools(false), 1419 mForceNonNativeTheme(false), 1420 mIsSrcdocDocument(false), 1421 mHasDisplayDocument(false), 1422 mFontFaceSetDirty(true), 1423 mDidFireDOMContentLoaded(true), 1424 mIsTopLevelContentDocument(false), 1425 mIsContentDocument(false), 1426 mDidCallBeginLoad(false), 1427 mEncodingMenuDisabled(false), 1428 mLinksEnabled(true), 1429 mIsSVGGlyphsDocument(false), 1430 mInDestructor(false), 1431 mIsGoingAway(false), 1432 mStyleSetFilled(false), 1433 mQuirkSheetAdded(false), 1434 mMayHaveTitleElement(false), 1435 mDOMLoadingSet(false), 1436 mDOMInteractiveSet(false), 1437 mDOMCompleteSet(false), 1438 mAutoFocusFired(false), 1439 mScrolledToRefAlready(false), 1440 mChangeScrollPosWhenScrollingToRef(false), 1441 mDelayFrameLoaderInitialization(false), 1442 mSynchronousDOMContentLoaded(false), 1443 mMaybeServiceWorkerControlled(false), 1444 mAllowZoom(false), 1445 mValidScaleFloat(false), 1446 mValidMinScale(false), 1447 mValidMaxScale(false), 1448 mWidthStrEmpty(false), 1449 mLockingImages(false), 1450 mAnimatingImages(true), 1451 mParserAborted(false), 1452 mReportedDocumentUseCounters(false), 1453 mHasReportedShadowDOMUsage(false), 1454 mLoadEventFiring(false), 1455 mSkipLoadEventAfterClose(false), 1456 mDisableCookieAccess(false), 1457 mDisableDocWrite(false), 1458 mTooDeepWriteRecursion(false), 1459 mPendingMaybeEditingStateChanged(false), 1460 mHasBeenEditable(false), 1461 mIsRunningExecCommandByContent(false), 1462 mIsRunningExecCommandByChromeOrAddon(false), 1463 mSetCompleteAfterDOMContentLoaded(false), 1464 mDidHitCompleteSheetCache(false), 1465 mUseCountersInitialized(false), 1466 mShouldReportUseCounters(false), 1467 mShouldSendPageUseCounters(false), 1468 mUserHasInteracted(false), 1469 mHasUserInteractionTimerScheduled(false), 1470 mShouldResistFingerprinting(false), 1471 mIsInPrivateBrowsing(false), 1472 mCloningForSVGUse(false), 1473 mAllowDeclarativeShadowRoots(false), 1474 mSuspendDOMNotifications(false), 1475 mForceLoadAtTop(false), 1476 mSuppressNotifyingDevToolsOfNodeRemovals(false), 1477 mHasPolicyWithRequireTrustedTypesForDirective(false), 1478 mClipboardCopyTriggered(false), 1479 mXMLDeclarationBits(0), 1480 mOnloadBlockCount(0), 1481 mWriteLevel(0), 1482 mContentEditableCount(0), 1483 mEditingState(EditingState::eOff), 1484 mCompatMode(eCompatibility_FullStandards), 1485 mReadyState(ReadyState::READYSTATE_UNINITIALIZED), 1486 mAncestorIsLoading(false), 1487 mVisibilityState(dom::VisibilityState::Hidden), 1488 mType(eUnknown), 1489 mDefaultElementType(0), 1490 mAllowXULXBL(eTriUnset), 1491 mSkipDTDSecurityChecks(false), 1492 mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS), 1493 mSandboxFlags(0), 1494 mPartID(0), 1495 mMarkedCCGeneration(0), 1496 mPresShell(nullptr), 1497 mPreloadPictureDepth(0), 1498 mEventsSuppressed(0), 1499 mIgnoreDestructiveWritesCounter(0), 1500 mStaticCloneCount(0), 1501 mWindow(nullptr), 1502 mBFCacheEntry(nullptr), 1503 mInSyncOperationCount(0), 1504 mBlockDOMContentLoaded(0), 1505 mUpdateNestLevel(0), 1506 mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED), 1507 mViewportType(Unknown), 1508 mViewportFit(ViewportFitType::Auto), 1509 mInteractiveWidgetMode( 1510 InteractiveWidgetUtils::DefaultInteractiveWidgetMode()), 1511 mHeaderData(nullptr), 1512 mLanguageFromCharset(nullptr), 1513 mServoRestyleRootDirtyBits(0), 1514 mThrowOnDynamicMarkupInsertionCounter(0), 1515 mIgnoreOpensDuringUnloadCounter(0), 1516 mSavedResolution(1.0f), 1517 mClassificationFlags({0, 0}), 1518 mGeneration(0), 1519 mCachedTabSizeGeneration(0), 1520 mNextFormNumber(0), 1521 mNextControlNumber(0), 1522 mPreloadService(this), 1523 mShouldNotifyFetchSuccess(false), 1524 mShouldNotifyFormOrPasswordRemoved(false) { 1525 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this)); 1526 1527 SetIsInDocument(); 1528 SetIsConnected(true); 1529 1530 // Create these unconditionally, they will be used to warn about the `zoom` 1531 // property, even if use counters are disabled. 1532 mStyleUseCounters.reset(Servo_UseCounters_Create()); 1533 1534 SetContentType(nsDependentCString(aContentType)); 1535 1536 // Start out mLastStyleSheetSet as null, per spec 1537 SetDOMStringToNull(mLastStyleSheetSet); 1538 1539 // void state used to differentiate an empty source from an unselected source 1540 mPreloadPictureFoundSource.SetIsVoid(true); 1541 1542 RecomputeLanguageFromCharset(); 1543 1544 mPreloadReferrerInfo = new dom::ReferrerInfo(nullptr); 1545 mReferrerInfo = new dom::ReferrerInfo(nullptr); 1546 } 1547 1548 #ifndef ANDROID 1549 // unused by GeckoView 1550 static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) { 1551 if (NS_WARN_IF(!aWin)) { 1552 return false; 1553 } 1554 1555 nsIURI* uri = aWin->GetDocumentURI(); 1556 if (NS_WARN_IF(!uri)) { 1557 return false; 1558 } 1559 // getSpec is an expensive operation, hence we first check the scheme 1560 // to see if the caller is actually an about: page. 1561 if (!uri->SchemeIs("about")) { 1562 return false; 1563 } 1564 1565 nsAutoCString aboutSpec; 1566 nsresult rv = NS_GetAboutModuleName(uri, aboutSpec); 1567 NS_ENSURE_SUCCESS(rv, false); 1568 1569 return aboutSpec.EqualsASCII(aSpec); 1570 } 1571 #endif 1572 1573 bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) { 1574 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject); 1575 #ifdef ANDROID 1576 // GeckoView uses data URLs for error pages, so for now just check for any 1577 // error page 1578 return win && win->GetDocument() && win->GetDocument()->IsErrorPage(); 1579 #else 1580 return win && IsAboutErrorPage(win, "neterror"); 1581 #endif 1582 } 1583 1584 bool Document::CallerIsTrustedAboutHttpsOnlyError(JSContext* aCx, 1585 JSObject* aObject) { 1586 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject); 1587 #ifdef ANDROID 1588 // GeckoView uses data URLs for error pages, so for now just check for any 1589 // error page 1590 return win && win->GetDocument() && win->GetDocument()->IsErrorPage(); 1591 #else 1592 return win && IsAboutErrorPage(win, "httpsonlyerror"); 1593 #endif 1594 } 1595 1596 already_AddRefed<mozilla::dom::Promise> Document::AddCertException( 1597 bool aIsTemporary, ErrorResult& aError) { 1598 RefPtr<Promise> promise = Promise::Create(GetScopeObject(), aError, 1599 Promise::ePropagateUserInteraction); 1600 if (aError.Failed()) { 1601 return nullptr; 1602 } 1603 1604 nsresult rv = NS_OK; 1605 if (NS_WARN_IF(!mFailedChannel)) { 1606 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 1607 return promise.forget(); 1608 } 1609 1610 nsCOMPtr<nsIURI> failedChannelURI; 1611 NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI)); 1612 if (!failedChannelURI) { 1613 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 1614 return promise.forget(); 1615 } 1616 1617 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(failedChannelURI); 1618 if (!innerURI) { 1619 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 1620 return promise.forget(); 1621 } 1622 1623 nsAutoCString host; 1624 innerURI->GetAsciiHost(host); 1625 int32_t port; 1626 innerURI->GetPort(&port); 1627 1628 nsCOMPtr<nsITransportSecurityInfo> tsi; 1629 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi)); 1630 if (NS_WARN_IF(NS_FAILED(rv))) { 1631 promise->MaybeReject(rv); 1632 return promise.forget(); 1633 } 1634 if (NS_WARN_IF(!tsi)) { 1635 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 1636 return promise.forget(); 1637 } 1638 1639 nsCOMPtr<nsIX509Cert> cert; 1640 rv = tsi->GetServerCert(getter_AddRefs(cert)); 1641 if (NS_WARN_IF(NS_FAILED(rv))) { 1642 promise->MaybeReject(rv); 1643 return promise.forget(); 1644 } 1645 if (NS_WARN_IF(!cert)) { 1646 promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 1647 return promise.forget(); 1648 } 1649 1650 if (XRE_IsContentProcess()) { 1651 ContentChild* cc = ContentChild::GetSingleton(); 1652 MOZ_ASSERT(cc); 1653 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef(); 1654 cc->SendAddCertException(cert, host, port, attrs, aIsTemporary) 1655 ->Then(GetCurrentSerialEventTarget(), __func__, 1656 [promise](const mozilla::MozPromise< 1657 nsresult, mozilla::ipc::ResponseRejectReason, 1658 true>::ResolveOrRejectValue& aValue) { 1659 if (aValue.IsResolve()) { 1660 promise->MaybeResolve(aValue.ResolveValue()); 1661 } else { 1662 promise->MaybeRejectWithUndefined(); 1663 } 1664 }); 1665 return promise.forget(); 1666 } 1667 1668 if (XRE_IsParentProcess()) { 1669 nsCOMPtr<nsICertOverrideService> overrideService = 1670 do_GetService(NS_CERTOVERRIDE_CONTRACTID); 1671 if (!overrideService) { 1672 promise->MaybeReject(NS_ERROR_FAILURE); 1673 return promise.forget(); 1674 } 1675 1676 OriginAttributes const& attrs = NodePrincipal()->OriginAttributesRef(); 1677 rv = overrideService->RememberValidityOverride(host, port, attrs, cert, 1678 aIsTemporary); 1679 if (NS_WARN_IF(NS_FAILED(rv))) { 1680 promise->MaybeReject(rv); 1681 return promise.forget(); 1682 } 1683 1684 promise->MaybeResolveWithUndefined(); 1685 return promise.forget(); 1686 } 1687 1688 promise->MaybeReject(NS_ERROR_FAILURE); 1689 return promise.forget(); 1690 } 1691 1692 void Document::ReloadWithHttpsOnlyException() { 1693 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) { 1694 wgc->SendReloadWithHttpsOnlyException(); 1695 } 1696 } 1697 1698 // Given an nsresult that is assumed to be synthesized by PSM and describes a 1699 // certificate or TLS error, attempts to convert it into a string 1700 // representation of the underlying NSS error. 1701 // `aErrorCodeString` will be an empty string if `aResult` is not an error from 1702 // PSM or it does not represent a valid NSS error. 1703 void GetErrorCodeStringFromNSResult(nsresult aResult, 1704 nsAString& aErrorCodeString) { 1705 aErrorCodeString.Truncate(); 1706 1707 if (NS_ERROR_GET_MODULE(aResult) != NS_ERROR_MODULE_SECURITY || 1708 NS_ERROR_GET_SEVERITY(aResult) != NS_ERROR_SEVERITY_ERROR) { 1709 return; 1710 } 1711 1712 PRErrorCode errorCode = -1 * NS_ERROR_GET_CODE(aResult); 1713 if (!mozilla::psm::IsNSSErrorCode(errorCode)) { 1714 return; 1715 } 1716 1717 const char* errorCodeString = PR_ErrorToName(errorCode); 1718 if (!errorCodeString) { 1719 return; 1720 } 1721 1722 aErrorCodeString.AssignASCII(errorCodeString); 1723 } 1724 1725 void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) { 1726 nsresult rv = NS_OK; 1727 if (NS_WARN_IF(!mFailedChannel)) { 1728 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1729 return; 1730 } 1731 1732 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mFailedChannel)); 1733 1734 // We don't throw even if httpChannel is null, we just keep responseStatus and 1735 // responseStatusText empty 1736 if (httpChannel) { 1737 uint32_t responseStatus; 1738 nsAutoCString responseStatusText; 1739 rv = httpChannel->GetResponseStatus(&responseStatus); 1740 if (NS_SUCCEEDED(rv)) { 1741 aInfo.mResponseStatus = responseStatus; 1742 } 1743 1744 rv = httpChannel->GetResponseStatusText(responseStatusText); 1745 if (NS_FAILED(rv) || responseStatusText.IsEmpty()) { 1746 net_GetDefaultStatusTextForCode(responseStatus, responseStatusText); 1747 } 1748 aInfo.mResponseStatusText.AssignASCII(responseStatusText); 1749 } 1750 1751 nsCOMPtr<nsITransportSecurityInfo> tsi; 1752 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi)); 1753 if (NS_WARN_IF(NS_FAILED(rv))) { 1754 aRv.Throw(rv); 1755 return; 1756 } 1757 1758 nsresult channelStatus; 1759 rv = mFailedChannel->GetStatus(&channelStatus); 1760 if (NS_WARN_IF(NS_FAILED(rv))) { 1761 aRv.Throw(rv); 1762 return; 1763 } 1764 aInfo.mChannelStatus = static_cast<uint32_t>(channelStatus); 1765 1766 // If nsITransportSecurityInfo is not set, simply keep the remaining fields 1767 // empty (to make responseStatus and responseStatusText accessible). 1768 if (!tsi) { 1769 return; 1770 } 1771 1772 // TransportSecurityInfo::GetErrorCodeString always returns NS_OK 1773 (void)tsi->GetErrorCodeString(aInfo.mErrorCodeString); 1774 if (aInfo.mErrorCodeString.IsEmpty()) { 1775 GetErrorCodeStringFromNSResult(channelStatus, aInfo.mErrorCodeString); 1776 } 1777 } 1778 1779 bool Document::CallerIsTrustedAboutCertError(JSContext* aCx, 1780 JSObject* aObject) { 1781 nsGlobalWindowInner* win = xpc::WindowOrNull(aObject); 1782 #ifdef ANDROID 1783 // GeckoView uses data URLs for error pages, so for now just check for any 1784 // error page 1785 return win && win->GetDocument() && win->GetDocument()->IsErrorPage(); 1786 #else 1787 return win && IsAboutErrorPage(win, "certerror"); 1788 #endif 1789 } 1790 1791 bool Document::CallerIsSystemPrincipalOrWebCompatAddon(JSContext* aCx, 1792 JSObject* aObject) { 1793 RefPtr<BasePrincipal> principal = 1794 BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(aCx)); 1795 1796 if (!principal) { 1797 return false; 1798 } 1799 1800 // We allow the privileged APIs to be called from system principal. 1801 if (principal->IsSystemPrincipal()) { 1802 return true; 1803 } 1804 1805 // We only allow calling privileged APIs from the webcompat extension. 1806 if (auto* policy = principal->ContentScriptAddonPolicy()) { 1807 nsAutoString addonID; 1808 policy->GetId(addonID); 1809 1810 return addonID.EqualsLiteral("webcompat@mozilla.org"); 1811 } 1812 1813 return false; 1814 } 1815 1816 bool Document::IsErrorPage() const { 1817 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr; 1818 return loadInfo && loadInfo->GetLoadErrorPage(); 1819 } 1820 1821 void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo, 1822 ErrorResult& aRv) { 1823 nsresult rv = NS_OK; 1824 if (NS_WARN_IF(!mFailedChannel)) { 1825 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1826 return; 1827 } 1828 1829 nsCOMPtr<nsITransportSecurityInfo> tsi; 1830 rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(tsi)); 1831 if (NS_WARN_IF(NS_FAILED(rv))) { 1832 aRv.Throw(rv); 1833 return; 1834 } 1835 if (NS_WARN_IF(!tsi)) { 1836 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1837 return; 1838 } 1839 1840 nsresult channelStatus; 1841 rv = mFailedChannel->GetStatus(&channelStatus); 1842 if (NS_WARN_IF(NS_FAILED(rv))) { 1843 aRv.Throw(rv); 1844 return; 1845 } 1846 aInfo.mChannelStatus = static_cast<uint32_t>(channelStatus); 1847 1848 // TransportSecurityInfo::GetErrorCodeString always returns NS_OK 1849 (void)tsi->GetErrorCodeString(aInfo.mErrorCodeString); 1850 if (aInfo.mErrorCodeString.IsEmpty()) { 1851 GetErrorCodeStringFromNSResult(channelStatus, aInfo.mErrorCodeString); 1852 } 1853 1854 nsITransportSecurityInfo::OverridableErrorCategory errorCategory; 1855 rv = tsi->GetOverridableErrorCategory(&errorCategory); 1856 if (NS_WARN_IF(NS_FAILED(rv))) { 1857 aRv.Throw(rv); 1858 return; 1859 } 1860 switch (errorCategory) { 1861 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST: 1862 aInfo.mOverridableErrorCategory = 1863 dom::OverridableErrorCategory::Trust_error; 1864 break; 1865 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN: 1866 aInfo.mOverridableErrorCategory = 1867 dom::OverridableErrorCategory::Domain_mismatch; 1868 break; 1869 case nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME: 1870 aInfo.mOverridableErrorCategory = 1871 dom::OverridableErrorCategory::Expired_or_not_yet_valid; 1872 break; 1873 default: 1874 aInfo.mOverridableErrorCategory = dom::OverridableErrorCategory::Unset; 1875 break; 1876 } 1877 1878 nsCOMPtr<nsIX509Cert> cert; 1879 nsCOMPtr<nsIX509CertValidity> validity; 1880 rv = tsi->GetServerCert(getter_AddRefs(cert)); 1881 if (NS_WARN_IF(NS_FAILED(rv))) { 1882 aRv.Throw(rv); 1883 return; 1884 } 1885 if (NS_WARN_IF(!cert)) { 1886 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1887 return; 1888 } 1889 1890 rv = cert->GetValidity(getter_AddRefs(validity)); 1891 if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!validity)) { 1892 aInfo.mValidNotBefore = 0; 1893 aInfo.mValidNotAfter = 0; 1894 } else { 1895 PRTime validityResult; 1896 rv = validity->GetNotBefore(&validityResult); 1897 if (NS_WARN_IF(NS_FAILED(rv))) { 1898 aInfo.mValidNotBefore = 0; 1899 } else { 1900 aInfo.mValidNotBefore = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC); 1901 } 1902 1903 rv = validity->GetNotAfter(&validityResult); 1904 if (NS_WARN_IF(NS_FAILED(rv))) { 1905 aInfo.mValidNotAfter = 0; 1906 } else { 1907 aInfo.mValidNotAfter = DOMTimeStamp(validityResult / PR_USEC_PER_MSEC); 1908 } 1909 } 1910 1911 nsAutoString issuerCommonName; 1912 nsAutoString certChainPEMString; 1913 Sequence<nsString>& certChainStrings = aInfo.mCertChainStrings.Construct(); 1914 int64_t maxValidity = std::numeric_limits<int64_t>::max(); 1915 int64_t minValidity = 0; 1916 PRTime notBefore, notAfter; 1917 nsTArray<RefPtr<nsIX509Cert>> handshakeCertificates; 1918 rv = tsi->GetHandshakeCertificates(handshakeCertificates); 1919 if (NS_WARN_IF(NS_FAILED(rv))) { 1920 aRv.Throw(rv); 1921 return; 1922 } 1923 1924 if (NS_WARN_IF(handshakeCertificates.IsEmpty())) { 1925 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1926 return; 1927 } 1928 1929 for (const auto& certificate : handshakeCertificates) { 1930 rv = certificate->GetIssuerCommonName(issuerCommonName); 1931 if (NS_WARN_IF(NS_FAILED(rv))) { 1932 aRv.Throw(rv); 1933 return; 1934 } 1935 1936 rv = certificate->GetValidity(getter_AddRefs(validity)); 1937 if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!validity)) { 1938 notBefore = 0; 1939 notAfter = 0; 1940 } else { 1941 rv = validity->GetNotBefore(¬Before); 1942 if (NS_WARN_IF(NS_FAILED(rv))) { 1943 notBefore = 0; 1944 } 1945 rv = validity->GetNotAfter(¬After); 1946 if (NS_WARN_IF(NS_FAILED(rv))) { 1947 notAfter = 0; 1948 } 1949 } 1950 1951 notBefore = std::max(minValidity, notBefore); 1952 notAfter = std::min(maxValidity, notAfter); 1953 nsTArray<uint8_t> certArray; 1954 rv = certificate->GetRawDER(certArray); 1955 if (NS_WARN_IF(NS_FAILED(rv))) { 1956 aRv.Throw(rv); 1957 return; 1958 } 1959 1960 nsAutoString der64; 1961 rv = Base64Encode(reinterpret_cast<const char*>(certArray.Elements()), 1962 certArray.Length(), der64); 1963 if (NS_WARN_IF(NS_FAILED(rv))) { 1964 aRv.Throw(rv); 1965 return; 1966 } 1967 if (!certChainStrings.AppendElement(der64, fallible)) { 1968 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 1969 return; 1970 } 1971 } 1972 1973 aInfo.mIssuerCommonName.Assign(issuerCommonName); 1974 aInfo.mCertValidityRangeNotAfter = DOMTimeStamp(notAfter / PR_USEC_PER_MSEC); 1975 aInfo.mCertValidityRangeNotBefore = 1976 DOMTimeStamp(notBefore / PR_USEC_PER_MSEC); 1977 1978 int32_t errorCode; 1979 rv = tsi->GetErrorCode(&errorCode); 1980 if (NS_WARN_IF(NS_FAILED(rv))) { 1981 aRv.Throw(rv); 1982 return; 1983 } 1984 1985 aInfo.mErrorIsOverridable = mozilla::psm::ErrorIsOverridable(errorCode); 1986 1987 nsCOMPtr<nsINSSErrorsService> nsserr = 1988 do_GetService("@mozilla.org/nss_errors_service;1"); 1989 if (NS_WARN_IF(!nsserr)) { 1990 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1991 return; 1992 } 1993 nsresult res; 1994 rv = nsserr->GetXPCOMFromNSSError(errorCode, &res); 1995 if (NS_WARN_IF(NS_FAILED(rv))) { 1996 aRv.Throw(rv); 1997 return; 1998 } 1999 rv = nsserr->GetErrorMessage(res, aInfo.mErrorMessage); 2000 if (NS_WARN_IF(NS_FAILED(rv))) { 2001 aRv.Throw(rv); 2002 return; 2003 } 2004 2005 OriginAttributes attrs; 2006 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(this, attrs); 2007 nsCOMPtr<nsIURI> aURI; 2008 mFailedChannel->GetURI(getter_AddRefs(aURI)); 2009 if (XRE_IsContentProcess()) { 2010 ContentChild* cc = ContentChild::GetSingleton(); 2011 MOZ_ASSERT(cc); 2012 cc->SendIsSecureURI(aURI, attrs, &aInfo.mHasHSTS); 2013 } else { 2014 nsCOMPtr<nsISiteSecurityService> sss = 2015 do_GetService(NS_SSSERVICE_CONTRACTID); 2016 if (NS_WARN_IF(!sss)) { 2017 return; 2018 } 2019 (void)NS_WARN_IF(NS_FAILED(sss->IsSecureURI(aURI, attrs, &aInfo.mHasHSTS))); 2020 } 2021 nsCOMPtr<nsIPublicKeyPinningService> pkps = 2022 do_GetService(NS_PKPSERVICE_CONTRACTID); 2023 if (NS_WARN_IF(!pkps)) { 2024 return; 2025 } 2026 (void)NS_WARN_IF(NS_FAILED(pkps->HostHasPins(aURI, &aInfo.mHasHPKP))); 2027 } 2028 2029 bool Document::IsAboutPage() const { 2030 return NodePrincipal()->SchemeIs("about"); 2031 } 2032 2033 void Document::ConstructUbiNode(void* storage) { 2034 JS::ubi::Concrete<Document>::construct(storage, this); 2035 } 2036 2037 void Document::LoadEventFired() { 2038 // Collect page load timings 2039 AccumulatePageLoadTelemetry(); 2040 2041 // Record page load event 2042 RecordPageLoadEventTelemetry(); 2043 2044 // Release the JS bytecode cache from its wait on the load event, and 2045 // potentially dispatch the encoding of the bytecode. 2046 if (mScriptLoader) { 2047 mScriptLoader->LoadEventFired(); 2048 } 2049 } 2050 2051 void Document::RecordPageLoadEventTelemetry() { 2052 // If the page load time is empty, then the content wasn't something we want 2053 // to report (i.e. not a top level document). 2054 if (!mPageloadEventData.HasLoadTime()) { 2055 return; 2056 } 2057 MOZ_ASSERT(IsTopLevelContentDocument()); 2058 2059 nsPIDOMWindowOuter* window = GetWindow(); 2060 if (!window) { 2061 return; 2062 } 2063 2064 nsIDocShell* docshell = window->GetDocShell(); 2065 if (!docshell) { 2066 return; 2067 } 2068 2069 // Don't send any event telemetry for private browsing. 2070 if (IsInPrivateBrowsing()) { 2071 return; 2072 } 2073 2074 if (!GetChannel()) { 2075 return; 2076 } 2077 2078 auto pageloadEventType = performance::pageload_event::GetPageloadEventType(); 2079 2080 // Return if we are not sending an event for this pageload. 2081 if (pageloadEventType == mozilla::PageloadEventType::kNone) { 2082 return; 2083 } 2084 2085 #ifdef ACCESSIBILITY 2086 if (GetAccService() != nullptr) { 2087 mPageloadEventData.SetUserFeature( 2088 performance::pageload_event::UserFeature::USING_A11Y); 2089 } 2090 #endif 2091 2092 if (GetChannel()) { 2093 nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel = 2094 do_QueryInterface(GetChannel()); 2095 if (cacheInfoChannel) { 2096 nsICacheInfoChannel::CacheDisposition disposition = 2097 nsICacheInfoChannel::kCacheUnknown; 2098 nsresult rv = cacheInfoChannel->GetCacheDisposition(&disposition); 2099 if (NS_SUCCEEDED(rv)) { 2100 mPageloadEventData.set_cacheDisposition(disposition); 2101 } 2102 } 2103 } 2104 2105 nsAutoCString loadTypeStr; 2106 switch (docshell->GetLoadType()) { 2107 case LOAD_NORMAL: 2108 case LOAD_NORMAL_REPLACE: 2109 case LOAD_NORMAL_BYPASS_CACHE: 2110 case LOAD_NORMAL_BYPASS_PROXY: 2111 case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: 2112 loadTypeStr.Append("NORMAL"); 2113 break; 2114 case LOAD_HISTORY: 2115 loadTypeStr.Append("HISTORY"); 2116 break; 2117 case LOAD_RELOAD_NORMAL: 2118 case LOAD_RELOAD_BYPASS_CACHE: 2119 case LOAD_RELOAD_BYPASS_PROXY: 2120 case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: 2121 case LOAD_REFRESH: 2122 case LOAD_REFRESH_REPLACE: 2123 case LOAD_RELOAD_CHARSET_CHANGE: 2124 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE: 2125 case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE: 2126 loadTypeStr.Append("RELOAD"); 2127 break; 2128 case LOAD_LINK: 2129 loadTypeStr.Append("LINK"); 2130 break; 2131 case LOAD_STOP_CONTENT: 2132 case LOAD_STOP_CONTENT_AND_REPLACE: 2133 loadTypeStr.Append("STOP"); 2134 break; 2135 case LOAD_ERROR_PAGE: 2136 loadTypeStr.Append("ERROR"); 2137 break; 2138 default: 2139 loadTypeStr.Append("OTHER"); 2140 break; 2141 } 2142 mPageloadEventData.set_loadType(loadTypeStr); 2143 2144 nsCOMPtr<nsIEffectiveTLDService> tldService = 2145 mozilla::components::EffectiveTLD::Service(); 2146 2147 nsresult rv = NS_OK; 2148 if (tldService) { 2149 if (mReferrerInfo && 2150 (docshell->GetLoadType() & nsIDocShell::LOAD_CMD_NORMAL)) { 2151 nsAutoCString currentBaseDomain, referrerBaseDomain; 2152 nsCOMPtr<nsIURI> referrerURI = mReferrerInfo->GetComputedReferrer(); 2153 if (referrerURI) { 2154 rv = tldService->GetBaseDomain(referrerURI, 0, referrerBaseDomain); 2155 if (NS_SUCCEEDED(rv)) { 2156 bool sameOrigin = false; 2157 NodePrincipal()->IsSameOrigin(referrerURI, &sameOrigin); 2158 mPageloadEventData.set_sameOriginNav(sameOrigin); 2159 } 2160 } 2161 } 2162 } 2163 2164 if (pageloadEventType == PageloadEventType::kDomain) { 2165 // Do not record anything if we failed to assign the domain. 2166 if (!mPageloadEventData.MaybeSetPublicRegistrableDomain(GetDocumentURI(), 2167 GetChannel())) { 2168 return; 2169 } 2170 } 2171 2172 // Collect any JS timers that were measured during pageload. 2173 if (GetScopeObject() && GetScopeObject()->GetGlobalJSObject()) { 2174 AutoJSContext cx; 2175 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject(); 2176 JSAutoRealm ar(cx, globalObject); 2177 JS::JSTimers timers = JS::GetJSTimers(cx); 2178 2179 if (!timers.executionTime.IsZero()) { 2180 mPageloadEventData.set_jsExecTime( 2181 static_cast<uint32_t>(timers.executionTime.ToMilliseconds())); 2182 } 2183 2184 if (!timers.delazificationTime.IsZero()) { 2185 mPageloadEventData.set_delazifyTime( 2186 static_cast<uint32_t>(timers.delazificationTime.ToMilliseconds())); 2187 } 2188 } 2189 2190 // Sending a glean ping must be done on the parent process. 2191 if (ContentChild* cc = ContentChild::GetSingleton()) { 2192 if (GetNavigationTiming()) { 2193 uint64_t androidAppLinkLoadIdentifier = 0; 2194 #ifdef ANDROID 2195 if (BrowsingContext* bc = GetBrowsingContext()) { 2196 Maybe<uint64_t> contextAppLinkLoadIdentifier = 2197 bc->GetAndroidAppLinkLoadIdentifier(); 2198 if (contextAppLinkLoadIdentifier.isSome()) { 2199 androidAppLinkLoadIdentifier = contextAppLinkLoadIdentifier.value(); 2200 } 2201 } 2202 #endif 2203 cc->SendRecordPageLoadEvent( 2204 mPageloadEventData, 2205 GetNavigationTiming()->GetNavigationStartTimeStamp(), 2206 androidAppLinkLoadIdentifier); 2207 } 2208 } 2209 } 2210 2211 #ifndef ANDROID 2212 static void AccumulatePriorityFcpGleanPref( 2213 const nsCString& http3WithPriorityKey, const TimeDuration& duration) { 2214 if (http3WithPriorityKey == "with_priority"_ns) { 2215 glean::performance_pageload::h3p_fcp_with_priority.AccumulateRawDuration( 2216 duration); 2217 } else if (http3WithPriorityKey == "without_priority"_ns) { 2218 glean::performance_pageload::http3_fcp_without_priority 2219 .AccumulateRawDuration(duration); 2220 } else { 2221 MOZ_ASSERT_UNREACHABLE("Unknown value for http3WithPriorityKey"); 2222 } 2223 } 2224 #endif 2225 2226 void Document::AccumulatePageLoadTelemetry() { 2227 // Interested only in top level documents for real websites that are in the 2228 // foreground. 2229 if (!ShouldIncludeInTelemetry() || !IsTopLevelContentDocument() || 2230 !GetNavigationTiming() || 2231 !GetNavigationTiming()->DocShellHasBeenActiveSinceNavigationStart()) { 2232 return; 2233 } 2234 2235 if (!GetChannel()) { 2236 return; 2237 } 2238 2239 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel())); 2240 if (!timedChannel) { 2241 return; 2242 } 2243 2244 // Default duration is 0, use this to check for bogus negative values. 2245 const TimeDuration zeroDuration; 2246 2247 TimeStamp responseStart; 2248 timedChannel->GetResponseStart(&responseStart); 2249 2250 TimeStamp redirectStart, redirectEnd; 2251 timedChannel->GetRedirectStart(&redirectStart); 2252 timedChannel->GetRedirectEnd(&redirectEnd); 2253 2254 uint8_t redirectCount; 2255 timedChannel->GetRedirectCount(&redirectCount); 2256 if (redirectCount) { 2257 mPageloadEventData.set_redirectCount(static_cast<uint32_t>(redirectCount)); 2258 } 2259 2260 if (!redirectStart.IsNull() && !redirectEnd.IsNull()) { 2261 TimeDuration redirectTime = redirectEnd - redirectStart; 2262 if (redirectTime > zeroDuration) { 2263 mPageloadEventData.set_redirectTime( 2264 static_cast<uint32_t>(redirectTime.ToMilliseconds())); 2265 } 2266 } 2267 2268 TimeStamp dnsLookupStart, dnsLookupEnd; 2269 timedChannel->GetDomainLookupStart(&dnsLookupStart); 2270 timedChannel->GetDomainLookupEnd(&dnsLookupEnd); 2271 2272 if (!dnsLookupStart.IsNull() && !dnsLookupEnd.IsNull()) { 2273 TimeDuration dnsLookupTime = dnsLookupEnd - dnsLookupStart; 2274 if (dnsLookupTime > zeroDuration) { 2275 mPageloadEventData.set_dnsLookupTime( 2276 static_cast<uint32_t>(dnsLookupTime.ToMilliseconds())); 2277 } 2278 } 2279 2280 TimeStamp navigationStart = 2281 GetNavigationTiming()->GetNavigationStartTimeStamp(); 2282 2283 if (!navigationStart) { 2284 return; 2285 } 2286 2287 if (!responseStart) { 2288 // This happens when getting a response from the cache. 2289 responseStart = navigationStart; 2290 } 2291 2292 nsAutoCString dnsKey("Native"); 2293 nsAutoCString http3Key; 2294 nsAutoCString http3WithPriorityKey; 2295 nsAutoCString earlyHintKey; 2296 nsCOMPtr<nsIHttpChannelInternal> httpChannel = 2297 do_QueryInterface(GetChannel()); 2298 if (httpChannel) { 2299 bool resolvedByTRR = false; 2300 (void)httpChannel->GetIsResolvedByTRR(&resolvedByTRR); 2301 if (resolvedByTRR) { 2302 if (nsCOMPtr<nsIDNSService> dns = 2303 do_GetService(NS_DNSSERVICE_CONTRACTID)) { 2304 dns->GetTRRDomainKey(dnsKey); 2305 } else { 2306 // Failed to get the DNS service. 2307 dnsKey = "(fail)"_ns; 2308 } 2309 mPageloadEventData.set_trrDomain(dnsKey); 2310 } 2311 2312 uint32_t major; 2313 uint32_t minor; 2314 if (NS_SUCCEEDED(httpChannel->GetResponseVersion(&major, &minor))) { 2315 if (major == 3) { 2316 http3Key = "http3"_ns; 2317 nsCOMPtr<nsIHttpChannel> httpChannel2 = do_QueryInterface(GetChannel()); 2318 nsCString header; 2319 if (httpChannel2 && 2320 NS_SUCCEEDED( 2321 httpChannel2->GetResponseHeader("priority"_ns, header)) && 2322 !header.IsEmpty()) { 2323 http3WithPriorityKey = "with_priority"_ns; 2324 } else { 2325 http3WithPriorityKey = "without_priority"_ns; 2326 } 2327 } else if (major == 2) { 2328 bool supportHttp3 = false; 2329 if (NS_FAILED(httpChannel->GetSupportsHTTP3(&supportHttp3))) { 2330 supportHttp3 = false; 2331 } 2332 if (supportHttp3) { 2333 http3Key = "supports_http3"_ns; 2334 } 2335 } 2336 2337 mPageloadEventData.set_httpVer(major); 2338 } 2339 2340 uint32_t earlyHintType = 0; 2341 (void)httpChannel->GetEarlyHintLinkType(&earlyHintType); 2342 if (earlyHintType & LinkStyle::ePRECONNECT) { 2343 earlyHintKey.Append("preconnect_"_ns); 2344 } 2345 if (earlyHintType & LinkStyle::ePRELOAD) { 2346 earlyHintKey.Append("preload_"_ns); 2347 earlyHintKey.Append(mPreloadService.GetEarlyHintUsed() ? "1"_ns : "0"_ns); 2348 } 2349 } 2350 2351 TimeStamp asyncOpen; 2352 timedChannel->GetAsyncOpen(&asyncOpen); 2353 if (asyncOpen) { 2354 glean::perf::dns_first_byte.Get(dnsKey).AccumulateRawDuration( 2355 responseStart - asyncOpen); 2356 } 2357 2358 // First Contentful Composite 2359 if (TimeStamp firstContentfulComposite = 2360 GetNavigationTiming()->GetFirstContentfulCompositeTimeStamp()) { 2361 glean::performance_pageload::fcp.AccumulateRawDuration( 2362 firstContentfulComposite - navigationStart); 2363 2364 if (!http3WithPriorityKey.IsEmpty()) { 2365 glean::perf::h3p_first_contentful_paint.Get(http3WithPriorityKey) 2366 .AccumulateRawDuration(firstContentfulComposite - navigationStart); 2367 #ifndef ANDROID 2368 AccumulatePriorityFcpGleanPref( 2369 http3WithPriorityKey, firstContentfulComposite - navigationStart); 2370 #endif 2371 } 2372 2373 glean::performance_pageload::fcp_responsestart.AccumulateRawDuration( 2374 firstContentfulComposite - responseStart); 2375 2376 TimeDuration fcpTime = firstContentfulComposite - navigationStart; 2377 if (fcpTime > zeroDuration) { 2378 mPageloadEventData.set_fcpTime( 2379 static_cast<uint32_t>(fcpTime.ToMilliseconds())); 2380 } 2381 } 2382 2383 // Report the most up to date LCP time. For our histogram we actually report 2384 // this on page unload. 2385 if (TimeStamp lcpTime = 2386 GetNavigationTiming()->GetLargestContentfulRenderTimeStamp()) { 2387 mPageloadEventData.set_lcpTime( 2388 static_cast<uint32_t>((lcpTime - navigationStart).ToMilliseconds())); 2389 } 2390 2391 // Load event 2392 if (TimeStamp loadEventStart = 2393 GetNavigationTiming()->GetLoadEventStartTimeStamp()) { 2394 glean::performance_pageload::load_time.AccumulateRawDuration( 2395 loadEventStart - navigationStart); 2396 2397 if (!http3WithPriorityKey.IsEmpty()) { 2398 glean::perf::h3p_page_load_time.Get(http3WithPriorityKey) 2399 .AccumulateRawDuration(loadEventStart - navigationStart); 2400 } 2401 2402 glean::performance_pageload::load_time_responsestart.AccumulateRawDuration( 2403 loadEventStart - responseStart); 2404 2405 TimeDuration responseTime = responseStart - navigationStart; 2406 if (responseTime > zeroDuration) { 2407 mPageloadEventData.set_responseTime( 2408 static_cast<uint32_t>(responseTime.ToMilliseconds())); 2409 } 2410 2411 TimeDuration loadTime = loadEventStart - navigationStart; 2412 if (loadTime > zeroDuration) { 2413 mPageloadEventData.set_loadTime( 2414 static_cast<uint32_t>(loadTime.ToMilliseconds())); 2415 } 2416 2417 TimeStamp requestStart; 2418 timedChannel->GetRequestStart(&requestStart); 2419 if (requestStart) { 2420 TimeDuration timeToRequestStart = requestStart - navigationStart; 2421 if (timeToRequestStart > zeroDuration) { 2422 mPageloadEventData.set_timeToRequestStart( 2423 static_cast<uint32_t>(timeToRequestStart.ToMilliseconds())); 2424 } else { 2425 // Speculative and pre-established connections may yield zero or 2426 // slightly negative timeToRequestStart timings. We record these as zero 2427 // to maintain consistent, non-negative timing data, while still 2428 // capturing the impact of early connection establishment. 2429 mPageloadEventData.set_timeToRequestStart(0); 2430 } 2431 } 2432 2433 TimeStamp secureConnectStart; 2434 TimeStamp connectEnd; 2435 timedChannel->GetSecureConnectionStart(&secureConnectStart); 2436 timedChannel->GetConnectEnd(&connectEnd); 2437 if (secureConnectStart && connectEnd) { 2438 TimeDuration tlsHandshakeTime = connectEnd - secureConnectStart; 2439 if (tlsHandshakeTime > zeroDuration) { 2440 mPageloadEventData.set_tlsHandshakeTime( 2441 static_cast<uint32_t>(tlsHandshakeTime.ToMilliseconds())); 2442 } 2443 } 2444 } 2445 } 2446 2447 Document::~Document() { 2448 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this)); 2449 MOZ_ASSERT(!IsTopLevelContentDocument() || !IsResourceDoc(), 2450 "Can't be top-level and a resource doc at the same time"); 2451 2452 NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document"); 2453 2454 if (IsTopLevelContentDocument()) { 2455 RemoveToplevelLoadingDocument(this); 2456 } 2457 2458 mInDestructor = true; 2459 mInUnlinkOrDeletion = true; 2460 2461 mozilla::DropJSObjects(this); 2462 2463 // Clear mObservers to keep it in sync with the mutationobserver list 2464 mObservers.Clear(); 2465 2466 mIntersectionObservers.Clear(); 2467 2468 if (mStyleSheetSetList) { 2469 mStyleSheetSetList->Disconnect(); 2470 } 2471 2472 if (mAnimationController) { 2473 mAnimationController->Disconnect(); 2474 } 2475 2476 mParentDocument = nullptr; 2477 2478 // Kill the subdocument map, doing this will release its strong 2479 // references, if any. 2480 mSubDocuments = nullptr; 2481 2482 nsAutoScriptBlocker scriptBlocker; 2483 2484 WillRemoveRoot(); 2485 2486 // Invalidate cached array of child nodes 2487 InvalidateChildNodes(); 2488 2489 // We should not have child nodes when destructor is called, 2490 // since child nodes keep their owner document alive. 2491 MOZ_ASSERT(!HasChildren()); 2492 2493 mCachedRootElement = nullptr; 2494 2495 for (auto& sheets : mAdditionalSheets) { 2496 UnlinkStyleSheets(sheets); 2497 } 2498 2499 if (mAttributeStyles) { 2500 mAttributeStyles->SetOwningDocument(nullptr); 2501 } 2502 2503 if (mListenerManager) { 2504 mListenerManager->Disconnect(); 2505 UnsetFlags(NODE_HAS_LISTENERMANAGER); 2506 } 2507 2508 if (mScriptLoader) { 2509 mScriptLoader->DropDocumentReference(); 2510 } 2511 2512 if (mCSSLoader) { 2513 // Could be null here if Init() failed or if we have been unlinked. 2514 mCSSLoader->DropDocumentReference(); 2515 } 2516 2517 if (mStyleImageLoader) { 2518 mStyleImageLoader->DropDocumentReference(); 2519 } 2520 2521 if (mXULBroadcastManager) { 2522 mXULBroadcastManager->DropDocumentReference(); 2523 } 2524 2525 if (mXULPersist) { 2526 mXULPersist->DropDocumentReference(); 2527 } 2528 2529 if (mPermissionDelegateHandler) { 2530 mPermissionDelegateHandler->DropDocumentReference(); 2531 } 2532 2533 SetLockingImages(false); 2534 SetImageAnimationState(false); 2535 2536 mHeaderData = nullptr; 2537 2538 mPendingTitleChangeEvent.Revoke(); 2539 2540 MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(), 2541 "must not have media query lists left"); 2542 2543 if (mNodeInfoManager) { 2544 mNodeInfoManager->DropDocumentReference(); 2545 } 2546 2547 if (mDocGroup) { 2548 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup()); 2549 mDocGroup->GetBrowsingContextGroup()->RemoveDocument(this, mDocGroup); 2550 } 2551 2552 UnlinkOriginalDocumentIfStatic(); 2553 2554 UnregisterFromMemoryReportingForDataDocument(); 2555 2556 if (isInList()) { 2557 MOZ_ASSERT(AllDocumentsList().contains(this)); 2558 remove(); 2559 } 2560 } 2561 2562 void Document::DropStyleSet() { mStyleSet = nullptr; } 2563 2564 NS_INTERFACE_TABLE_HEAD(Document) 2565 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY 2566 NS_INTERFACE_TABLE_BEGIN 2567 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(Document, nsISupports, nsINode) 2568 NS_INTERFACE_TABLE_ENTRY(Document, nsINode) 2569 NS_INTERFACE_TABLE_ENTRY(Document, Document) 2570 NS_INTERFACE_TABLE_ENTRY(Document, nsIScriptObjectPrincipal) 2571 NS_INTERFACE_TABLE_ENTRY(Document, EventTarget) 2572 NS_INTERFACE_TABLE_ENTRY(Document, nsISupportsWeakReference) 2573 NS_INTERFACE_TABLE_END 2574 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(Document) 2575 NS_INTERFACE_MAP_END 2576 2577 NS_IMPL_CYCLE_COLLECTING_ADDREF(Document) 2578 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Document, LastRelease()) 2579 2580 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Document) 2581 if (Element::CanSkip(tmp, aRemovingAllowed)) { 2582 EventListenerManager* elm = tmp->GetExistingListenerManager(); 2583 if (elm) { 2584 elm->MarkForCC(); 2585 } 2586 return true; 2587 } 2588 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END 2589 2590 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document) 2591 return Element::CanSkipInCC(tmp); 2592 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END 2593 2594 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document) 2595 return Element::CanSkipThis(tmp); 2596 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END 2597 2598 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document) 2599 if (MOZ_UNLIKELY(cb.WantDebugInfo())) { 2600 char name[512]; 2601 nsAutoCString loadedAsData; 2602 if (tmp->IsLoadedAsData()) { 2603 loadedAsData.AssignLiteral("data"); 2604 } else { 2605 loadedAsData.AssignLiteral("normal"); 2606 } 2607 uint32_t nsid = tmp->GetDefaultNamespaceID(); 2608 nsAutoCString uri; 2609 if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault(); 2610 static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)", 2611 "(xhtml)", "(XLink)", "(XSLT)", 2612 "(MathML)", "(RDF)", "(XUL)"}; 2613 if (nsid < std::size(kNSURIs)) { 2614 SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(), 2615 kNSURIs[nsid], uri.get()); 2616 } else { 2617 SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get()); 2618 } 2619 cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); 2620 } else { 2621 NS_IMPL_CYCLE_COLLECTION_DESCRIBE(Document, tmp->mRefCnt.get()) 2622 } 2623 2624 if (!nsINode::Traverse(tmp, cb)) { 2625 return NS_SUCCESS_INTERRUPTED_TRAVERSE; 2626 } 2627 2628 tmp->mExternalResourceMap.Traverse(&cb); 2629 2630 // Traverse all Document pointer members. 2631 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo) 2632 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument) 2633 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet) 2634 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle) 2635 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentL10n) 2636 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFragmentDirective) 2637 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHighlightRegistry) 2638 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFullscreenEvents) 2639 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser) 2640 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject) 2641 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager) 2642 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList) 2643 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader) 2644 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomContentContainer) 2645 2646 DocumentOrShadowRoot::Traverse(tmp, cb); 2647 2648 if (tmp->mRadioGroupContainer) { 2649 RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb); 2650 } 2651 2652 for (auto& sheets : tmp->mAdditionalSheets) { 2653 tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb); 2654 } 2655 2656 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker) 2657 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver) 2658 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementsObservedForLastRememberedSize) 2659 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation) 2660 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps) 2661 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise) 2662 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument) 2663 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder) 2664 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline) 2665 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker) 2666 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner) 2667 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection) 2668 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImages); 2669 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEmbeds); 2670 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLinks); 2671 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mForms); 2672 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScripts); 2673 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplets); 2674 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors); 2675 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents) 2676 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher) 2677 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy) 2678 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissionDelegateHandler) 2679 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener) 2680 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument) 2681 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager) 2682 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll) 2683 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActiveViewTransition) 2684 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mViewTransitionUpdateCallbacks) 2685 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup) 2686 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameRequestManager) 2687 2688 // Traverse all our nsCOMArrays. 2689 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages) 2690 2691 // Traverse animation components 2692 if (tmp->mAnimationController) { 2693 tmp->mAnimationController->Traverse(&cb); 2694 } 2695 2696 if (tmp->mSubDocuments) { 2697 for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) { 2698 auto entry = static_cast<SubDocMapEntry*>(iter.Get()); 2699 2700 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSubDocuments entry->mKey"); 2701 cb.NoteXPCOMChild(entry->mKey); 2702 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, 2703 "mSubDocuments entry->mSubDocument"); 2704 cb.NoteXPCOMChild(ToSupports(entry->mSubDocument)); 2705 } 2706 } 2707 2708 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader) 2709 2710 // We own only the items in mDOMMediaQueryLists that have listeners; 2711 // this reference is managed by their AddListener and RemoveListener 2712 // methods. 2713 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql; 2714 mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) { 2715 if (mql->HasListeners() && 2716 NS_SUCCEEDED(mql->CheckCurrentGlobalCorrectness())) { 2717 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item"); 2718 cb.NoteXPCOMChild(static_cast<EventTarget*>(mql)); 2719 } 2720 } 2721 2722 // XXX: This should be not needed once bug 1569185 lands. 2723 for (const auto& entry : tmp->mL10nProtoElements) { 2724 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mL10nProtoElements key"); 2725 cb.NoteXPCOMChild(entry.GetKey()); 2726 CycleCollectionNoteChild(cb, entry.GetWeak(), "mL10nProtoElements value"); 2727 } 2728 2729 for (size_t i = 0; i < tmp->mPendingFrameStaticClones.Length(); ++i) { 2730 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingFrameStaticClones[i].mElement); 2731 NS_IMPL_CYCLE_COLLECTION_TRAVERSE( 2732 mPendingFrameStaticClones[i].mStaticCloneOf); 2733 } 2734 2735 for (auto& tableEntry : tmp->mActiveLocks) { 2736 ImplCycleCollectionTraverse(cb, *tableEntry.GetModifiableData(), 2737 "mActiveLocks entry", 0); 2738 } 2739 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 2740 2741 NS_IMPL_CYCLE_COLLECTION_CLASS(Document) 2742 2743 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Document) 2744 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER 2745 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedStateObject) 2746 NS_IMPL_CYCLE_COLLECTION_TRACE_END 2747 2748 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document) 2749 tmp->mInUnlinkOrDeletion = true; 2750 2751 tmp->SetStateObject(nullptr); 2752 2753 // Clear out our external resources 2754 tmp->mExternalResourceMap.Shutdown(); 2755 2756 nsAutoScriptBlocker scriptBlocker; 2757 2758 tmp->RemoveCustomContentContainer(); 2759 2760 nsINode::Unlink(tmp); 2761 2762 BatchRemovalState state{}; 2763 while (nsCOMPtr<nsIContent> child = tmp->GetLastChild()) { 2764 // Hold a strong ref to the node when we remove it, because we may be 2765 // the last reference to it. 2766 // If this code changes, change the corresponding code in Document's 2767 // unlink impl and ContentUnbinder::UnbindSubtree. 2768 tmp->DisconnectChild(child); 2769 child->UnbindFromTree(/* aNewParent=*/nullptr, &state); 2770 state.mIsFirst = false; 2771 } 2772 2773 tmp->UnlinkOriginalDocumentIfStatic(); 2774 2775 tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer 2776 2777 tmp->SetScriptGlobalObject(nullptr); 2778 2779 for (auto& sheets : tmp->mAdditionalSheets) { 2780 tmp->UnlinkStyleSheets(sheets); 2781 } 2782 2783 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo) 2784 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument) 2785 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadObserver) 2786 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementsObservedForLastRememberedSize); 2787 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) 2788 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle) 2789 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentL10n) 2790 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFragmentDirective) 2791 NS_IMPL_CYCLE_COLLECTION_UNLINK(mHighlightRegistry) 2792 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingFullscreenEvents) 2793 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser) 2794 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOnloadBlocker) 2795 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation) 2796 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps) 2797 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise) 2798 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument) 2799 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder) 2800 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline) 2801 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker) 2802 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner) 2803 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection) 2804 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImages); 2805 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEmbeds); 2806 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLinks); 2807 NS_IMPL_CYCLE_COLLECTION_UNLINK(mForms); 2808 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScripts); 2809 NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplets); 2810 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchors); 2811 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContents) 2812 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher) 2813 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy) 2814 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPermissionDelegateHandler) 2815 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener) 2816 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument) 2817 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager) 2818 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll) 2819 NS_IMPL_CYCLE_COLLECTION_UNLINK(mActiveViewTransition) 2820 NS_IMPL_CYCLE_COLLECTION_UNLINK(mViewTransitionUpdateCallbacks) 2821 NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo) 2822 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo) 2823 2824 if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) { 2825 tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(tmp, 2826 tmp->mDocGroup); 2827 } 2828 tmp->mDocGroup = nullptr; 2829 2830 if (tmp->IsTopLevelContentDocument()) { 2831 RemoveToplevelLoadingDocument(tmp); 2832 } 2833 2834 tmp->mParentDocument = nullptr; 2835 2836 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages) 2837 2838 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers) 2839 2840 if (tmp->mListenerManager) { 2841 tmp->mListenerManager->Disconnect(); 2842 tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER); 2843 tmp->mListenerManager = nullptr; 2844 } 2845 2846 if (tmp->mStyleSheetSetList) { 2847 tmp->mStyleSheetSetList->Disconnect(); 2848 tmp->mStyleSheetSetList = nullptr; 2849 } 2850 2851 tmp->mSubDocuments = nullptr; 2852 2853 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameRequestManager) 2854 2855 DocumentOrShadowRoot::Unlink(tmp); 2856 2857 tmp->mRadioGroupContainer = nullptr; 2858 2859 // Document has a pretty complex destructor, so we're going to 2860 // assume that *most* cycles you actually want to break somewhere 2861 // else, and not unlink an awful lot here. 2862 2863 tmp->mExpandoAndGeneration.OwnerUnlinked(); 2864 2865 if (tmp->mAnimationController) { 2866 tmp->mAnimationController->Unlink(); 2867 } 2868 2869 tmp->mPendingTitleChangeEvent.Revoke(); 2870 2871 if (tmp->mCSSLoader) { 2872 tmp->mCSSLoader->DropDocumentReference(); 2873 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader) 2874 } 2875 2876 if (tmp->mScriptLoader) { 2877 tmp->mScriptLoader->DropDocumentReference(); 2878 NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader) 2879 } 2880 2881 // We own only the items in mDOMMediaQueryLists that have listeners; 2882 // this reference is managed by their AddListener and RemoveListener 2883 // methods. 2884 for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) { 2885 MediaQueryList* next = 2886 static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext(); 2887 mql->Disconnect(); 2888 mql = next; 2889 } 2890 2891 tmp->mPendingFrameStaticClones.Clear(); 2892 2893 tmp->mActiveLocks.Clear(); 2894 2895 if (tmp->isInList()) { 2896 MOZ_ASSERT(AllDocumentsList().contains(tmp)); 2897 tmp->remove(); 2898 } 2899 2900 tmp->mInUnlinkOrDeletion = false; 2901 2902 tmp->UnregisterFromMemoryReportingForDataDocument(); 2903 2904 NS_IMPL_CYCLE_COLLECTION_UNLINK(mL10nProtoElements) 2905 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR 2906 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE 2907 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 2908 2909 nsresult Document::Init(nsIPrincipal* aPrincipal, 2910 nsIPrincipal* aPartitionedPrincipal) { 2911 if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) { 2912 return NS_ERROR_ALREADY_INITIALIZED; 2913 } 2914 2915 // Force initialization. 2916 mOnloadBlocker = new OnloadBlocker(); 2917 2918 mNodeInfoManager = new nsNodeInfoManager(this, aPrincipal); 2919 2920 // mNodeInfo keeps NodeInfoManager alive! 2921 mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo(); 2922 NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY); 2923 MOZ_ASSERT(mNodeInfo->NodeType() == DOCUMENT_NODE, 2924 "Bad NodeType in aNodeInfo"); 2925 2926 NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!"); 2927 2928 if (!mLoadedAsData) { 2929 CreateCSSAndStyleImageLoaders(false); 2930 } 2931 2932 // If after creation the owner js global is not set for a document 2933 // we use the default compartment for this document, instead of creating 2934 // wrapper in some random compartment when the document is exposed to js 2935 // via some events. 2936 nsCOMPtr<nsIGlobalObject> global = 2937 xpc::NativeGlobal(xpc::PrivilegedJunkScope()); 2938 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE); 2939 mScopeObject = do_GetWeakReference(global); 2940 MOZ_ASSERT(mScopeObject); 2941 2942 if (!mLoadedAsData) { 2943 mScriptLoader = new dom::ScriptLoader(this); 2944 } 2945 2946 // we need to create a policy here so getting the policy within 2947 // ::Policy() can *always* return a non null policy 2948 mFeaturePolicy = new dom::FeaturePolicy(this); 2949 mFeaturePolicy->SetDefaultOrigin(NodePrincipal()); 2950 2951 if (aPrincipal) { 2952 SetPrincipals(aPrincipal, aPartitionedPrincipal); 2953 } else { 2954 RecomputeResistFingerprinting(); 2955 } 2956 2957 AllDocumentsList().insertBack(this); 2958 2959 return NS_OK; 2960 } 2961 2962 void Document::RemoveAllProperties() { PropertyTable().RemoveAllProperties(); } 2963 2964 void Document::RemoveAllPropertiesFor(nsINode* aNode) { 2965 PropertyTable().RemoveAllPropertiesFor(aNode); 2966 } 2967 2968 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) { 2969 nsCOMPtr<nsIURI> uri; 2970 nsCOMPtr<nsIPrincipal> principal; 2971 nsCOMPtr<nsIPrincipal> partitionedPrincipal; 2972 if (aChannel) { 2973 mIsInPrivateBrowsing = NS_UsePrivateBrowsing(aChannel); 2974 2975 // Note: this code is duplicated in PrototypeDocumentContentSink::Init and 2976 // nsScriptSecurityManager::GetChannelResultPrincipals. 2977 // Note: this should match the uri used for the OnNewURI call in 2978 // nsDocShell::CreateDocumentViewer. 2979 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); 2980 2981 nsIScriptSecurityManager* securityManager = 2982 nsContentUtils::GetSecurityManager(); 2983 if (securityManager) { 2984 securityManager->GetChannelResultPrincipals( 2985 aChannel, getter_AddRefs(principal), 2986 getter_AddRefs(partitionedPrincipal)); 2987 } 2988 } 2989 2990 bool equal = principal->Equals(partitionedPrincipal); 2991 2992 principal = MaybeDowngradePrincipal(principal); 2993 if (equal) { 2994 partitionedPrincipal = principal; 2995 } else { 2996 partitionedPrincipal = MaybeDowngradePrincipal(partitionedPrincipal); 2997 } 2998 2999 ResetToURI(uri, aLoadGroup, principal, partitionedPrincipal); 3000 3001 // Note that, since mTiming does not change during a reset, the 3002 // navigationStart time remains unchanged and therefore any future new 3003 // timeline will have the same global clock time as the old one. 3004 mDocumentTimeline = nullptr; 3005 3006 if (nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel)) { 3007 if (nsCOMPtr<nsIURI> baseURI = do_GetProperty(bag, u"baseURI"_ns)) { 3008 mDocumentBaseURI = baseURI.forget(); 3009 mChromeXHRDocBaseURI = nullptr; 3010 } 3011 } 3012 3013 mChannel = aChannel; 3014 RecomputeResistFingerprinting(); 3015 MaybeRecomputePartitionKey(); 3016 } 3017 3018 void Document::DisconnectNodeTree() { 3019 // Delete references to sub-documents and kill the subdocument map, 3020 // if any. This is not strictly needed, but makes the node tree 3021 // teardown a bit faster. 3022 mSubDocuments = nullptr; 3023 3024 bool oldVal = mInUnlinkOrDeletion; 3025 mInUnlinkOrDeletion = true; 3026 { // Scope for update 3027 MOZ_AUTO_DOC_UPDATE(this, true); 3028 3029 WillRemoveRoot(); 3030 3031 // Invalidate cached array of child nodes 3032 InvalidateChildNodes(); 3033 3034 while (nsCOMPtr<nsIContent> content = GetLastChild()) { 3035 nsMutationGuard::DidMutate(); 3036 MutationObservers::NotifyContentWillBeRemoved(this, content, {}); 3037 DisconnectChild(content); 3038 if (content == mCachedRootElement) { 3039 // Immediately clear mCachedRootElement, now that it's been removed 3040 // from mChildren, so that GetRootElement() will stop returning this 3041 // now-stale value. 3042 mCachedRootElement = nullptr; 3043 } 3044 content->UnbindFromTree(); 3045 } 3046 MOZ_ASSERT(!mCachedRootElement, 3047 "After removing all children, there should be no root elem"); 3048 } 3049 mInUnlinkOrDeletion = oldVal; 3050 } 3051 3052 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup, 3053 nsIPrincipal* aPrincipal, 3054 nsIPrincipal* aPartitionedPrincipal) { 3055 MOZ_ASSERT(aURI, "Null URI passed to ResetToURI"); 3056 MOZ_ASSERT(!!aPrincipal == !!aPartitionedPrincipal); 3057 3058 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, 3059 ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get())); 3060 3061 mSecurityInfo = nullptr; 3062 3063 nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup); 3064 if (!aLoadGroup || group != aLoadGroup) { 3065 mDocumentLoadGroup = nullptr; 3066 } 3067 3068 DisconnectNodeTree(); 3069 3070 // Reset our stylesheets 3071 ResetStylesheetsToURI(aURI); 3072 3073 // Release the listener manager 3074 if (mListenerManager) { 3075 mListenerManager->Disconnect(); 3076 mListenerManager = nullptr; 3077 } 3078 3079 // Release the stylesheets list. 3080 mDOMStyleSheets = nullptr; 3081 3082 // Release our principal after tearing down the document, rather than before. 3083 // This ensures that, during teardown, the document and the dying window 3084 // (which already nulled out its document pointer and cached the principal) 3085 // have matching principals. 3086 SetPrincipals(nullptr, nullptr); 3087 3088 // Clear the original URI so SetDocumentURI sets it. 3089 mOriginalURI = nullptr; 3090 3091 SetDocumentURI(aURI); 3092 mChromeXHRDocURI = nullptr; 3093 // If mDocumentBaseURI is null, Document::GetBaseURI() returns 3094 // mDocumentURI. 3095 mDocumentBaseURI = nullptr; 3096 mChromeXHRDocBaseURI = nullptr; 3097 mOnionLocationURI = nullptr; 3098 3099 if (aLoadGroup) { 3100 nsCOMPtr<nsIInterfaceRequestor> callbacks; 3101 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); 3102 if (callbacks) { 3103 nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks); 3104 if (loadContext) { 3105 // This is asserting that if we previously set mIsInPrivateBrowsing 3106 // to true from the channel in Document::Reset, that the loadContext 3107 // also believes it to be true. 3108 MOZ_ASSERT(!mIsInPrivateBrowsing || 3109 mIsInPrivateBrowsing == loadContext->UsePrivateBrowsing()); 3110 mIsInPrivateBrowsing = loadContext->UsePrivateBrowsing(); 3111 } 3112 } 3113 3114 mDocumentLoadGroup = do_GetWeakReference(aLoadGroup); 3115 // there was an assertion here that aLoadGroup was not null. This 3116 // is no longer valid: nsDocShell::SetDocument does not create a 3117 // load group, and it works just fine 3118 3119 // XXXbz what does "just fine" mean exactly? And given that there 3120 // is no nsDocShell::SetDocument, what is this talking about? 3121 3122 if (IsContentDocument()) { 3123 // Inform the associated request context about this load start so 3124 // any of its internal load progress flags gets reset. 3125 nsCOMPtr<nsIRequestContextService> rcsvc = 3126 net::RequestContextService::GetOrCreate(); 3127 if (rcsvc) { 3128 nsCOMPtr<nsIRequestContext> rc; 3129 rcsvc->GetRequestContextFromLoadGroup(aLoadGroup, getter_AddRefs(rc)); 3130 if (rc) { 3131 rc->BeginLoad(); 3132 } 3133 } 3134 } 3135 } 3136 3137 mLastModified.Truncate(); 3138 // XXXbz I guess we're assuming that the caller will either pass in 3139 // a channel with a useful type or call SetContentType? 3140 SetContentType(""_ns); 3141 mContentLanguage = nullptr; 3142 mBaseTarget.Truncate(); 3143 3144 mXMLDeclarationBits = 0; 3145 3146 // Now get our new principal 3147 if (aPrincipal) { 3148 SetPrincipals(aPrincipal, aPartitionedPrincipal); 3149 } else { 3150 nsIScriptSecurityManager* securityManager = 3151 nsContentUtils::GetSecurityManager(); 3152 if (securityManager) { 3153 nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer); 3154 3155 if (!loadContext && aLoadGroup) { 3156 nsCOMPtr<nsIInterfaceRequestor> cbs; 3157 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs)); 3158 loadContext = do_GetInterface(cbs); 3159 } 3160 3161 MOZ_ASSERT(loadContext, 3162 "must have a load context or pass in an explicit principal"); 3163 3164 nsCOMPtr<nsIPrincipal> principal; 3165 nsresult rv = securityManager->GetLoadContextContentPrincipal( 3166 mDocumentURI, loadContext, getter_AddRefs(principal)); 3167 if (NS_SUCCEEDED(rv)) { 3168 SetPrincipals(principal, principal); 3169 } 3170 } 3171 } 3172 3173 if (mFontFaceSet) { 3174 mFontFaceSet->RefreshStandardFontLoadPrincipal(); 3175 } 3176 3177 // Refresh the principal on the realm. 3178 if (nsPIDOMWindowInner* win = GetInnerWindow()) { 3179 nsGlobalWindowInner::Cast(win)->RefreshRealmPrincipal(); 3180 } 3181 } 3182 3183 already_AddRefed<nsIPrincipal> Document::MaybeDowngradePrincipal( 3184 nsIPrincipal* aPrincipal) { 3185 if (!aPrincipal) { 3186 return nullptr; 3187 } 3188 3189 // We can't load a document with an expanded principal. If we're given one, 3190 // automatically downgrade it to the last principal it subsumes (which is the 3191 // extension principal, in the case of extension content scripts). 3192 auto* basePrin = BasePrincipal::Cast(aPrincipal); 3193 if (basePrin->Is<ExpandedPrincipal>()) { 3194 MOZ_DIAGNOSTIC_CRASH( 3195 "Should never try to create a document with " 3196 "an expanded principal"); 3197 3198 auto* expanded = basePrin->As<ExpandedPrincipal>(); 3199 return do_AddRef(expanded->AllowList().LastElement()); 3200 } 3201 3202 if (aPrincipal->IsSystemPrincipal() && mDocumentContainer) { 3203 // We basically want the parent document here, but because this is very 3204 // early in the load, GetInProcessParentDocument() returns null, so we use 3205 // the docshell hierarchy to get this information instead. 3206 if (RefPtr<BrowsingContext> parent = 3207 mDocumentContainer->GetBrowsingContext()->GetParent()) { 3208 auto* parentWin = nsGlobalWindowOuter::Cast(parent->GetDOMWindow()); 3209 if (!parentWin || !parentWin->GetPrincipal()->IsSystemPrincipal()) { 3210 nsCOMPtr<nsIPrincipal> nullPrincipal = 3211 NullPrincipal::CreateWithoutOriginAttributes(); 3212 return nullPrincipal.forget(); 3213 } 3214 } 3215 } 3216 nsCOMPtr<nsIPrincipal> principal(aPrincipal); 3217 return principal.forget(); 3218 } 3219 3220 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) { 3221 ServoStyleSet& styleSet = EnsureStyleSet(); 3222 3223 // lowest index first 3224 const size_t newDocIndex = StyleOrderIndexOfSheet(aSheet); 3225 MOZ_ASSERT(newDocIndex != mStyleSheets.NoIndex); 3226 3227 size_t index = styleSet.SheetCount(StyleOrigin::Author); 3228 while (index--) { 3229 auto* sheet = styleSet.SheetAt(StyleOrigin::Author, index); 3230 MOZ_ASSERT(sheet); 3231 if (!sheet->GetAssociatedDocumentOrShadowRoot()) { 3232 // If the sheet is not owned by the document it should be an author sheet 3233 // registered at nsStyleSheetService, or an additional sheet. In that case 3234 // the doc sheet should end up before it. 3235 // FIXME(emilio): Additional stylesheets inconsistently end up with 3236 // associated document, depending on which code-path adds them. Fix this. 3237 MOZ_ASSERT( 3238 nsStyleSheetService::GetInstance()->AuthorStyleSheets()->Contains( 3239 sheet) || 3240 mAdditionalSheets[eAuthorSheet].Contains(sheet)); 3241 continue; 3242 } 3243 size_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet); 3244 if (MOZ_UNLIKELY(sheetDocIndex == mStyleSheets.NoIndex)) { 3245 MOZ_ASSERT_UNREACHABLE("Which stylesheet can hit this?"); 3246 continue; 3247 } 3248 MOZ_ASSERT(sheetDocIndex != newDocIndex); 3249 if (sheetDocIndex < newDocIndex) { 3250 // We found a document-owned sheet. All of them go together, so if the 3251 // current sheet goes before ours, we're at the right index already. 3252 return index + 1; 3253 } 3254 // Otherwise keep looking. Unfortunately we can't do something clever like: 3255 // 3256 // return index - sheetDocIndex + newDocIndex; 3257 // 3258 // Or so, because we need to deal with disabled / non-applicable sheets 3259 // which are not in the styleset, even though they're in the document. 3260 } 3261 // We found no sheet that goes before us, so we're index 0. 3262 return 0; 3263 } 3264 3265 void Document::ResetStylesheetsToURI(nsIURI* aURI) { 3266 MOZ_ASSERT(aURI); 3267 3268 ClearAdoptedStyleSheets(); 3269 ServoStyleSet& styleSet = EnsureStyleSet(); 3270 3271 auto ClearSheetList = [&](nsTArray<RefPtr<StyleSheet>>& aSheetList) { 3272 for (auto& sheet : Reversed(aSheetList)) { 3273 sheet->ClearAssociatedDocumentOrShadowRoot(); 3274 if (mStyleSetFilled) { 3275 styleSet.RemoveStyleSheet(*sheet); 3276 } 3277 } 3278 aSheetList.Clear(); 3279 }; 3280 ClearSheetList(mStyleSheets); 3281 for (auto& sheets : mAdditionalSheets) { 3282 ClearSheetList(sheets); 3283 } 3284 if (mStyleSetFilled) { 3285 if (auto* ss = nsStyleSheetService::GetInstance()) { 3286 for (auto& sheet : Reversed(*ss->AuthorStyleSheets())) { 3287 MOZ_ASSERT(!sheet->GetAssociatedDocumentOrShadowRoot()); 3288 if (sheet->IsApplicable()) { 3289 styleSet.RemoveStyleSheet(*sheet); 3290 } 3291 } 3292 } 3293 } 3294 3295 // Now reset our inline style and attribute sheets. 3296 if (mAttributeStyles) { 3297 mAttributeStyles->Reset(); 3298 mAttributeStyles->SetOwningDocument(this); 3299 } else { 3300 mAttributeStyles = new AttributeStyles(this); 3301 } 3302 3303 if (mStyleSetFilled) { 3304 FillStyleSetDocumentSheets(); 3305 3306 if (styleSet.StyleSheetsHaveChanged()) { 3307 ApplicableStylesChanged(); 3308 } 3309 } 3310 } 3311 3312 void Document::FillStyleSetUserAndUASheets() { 3313 // Make sure this does the same thing as PresShell::Add{User,Agent}Sheet wrt 3314 // ordering. 3315 3316 // The document will fill in the document sheets when we create the presshell 3317 auto* cache = GlobalStyleSheetCache::Singleton(); 3318 3319 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); 3320 MOZ_ASSERT(sheetService, 3321 "should never be creating a StyleSet after the style sheet " 3322 "service has gone"); 3323 3324 ServoStyleSet& styleSet = EnsureStyleSet(); 3325 for (StyleSheet* sheet : *sheetService->UserStyleSheets()) { 3326 styleSet.AppendStyleSheet(*sheet); 3327 } 3328 3329 StyleSheet* sheet = IsInChromeDocShell() ? cache->GetUserChromeSheet() 3330 : cache->GetUserContentSheet(); 3331 if (sheet) { 3332 styleSet.AppendStyleSheet(*sheet); 3333 } 3334 3335 styleSet.AppendStyleSheet(*cache->UASheet()); 3336 3337 if (MOZ_LIKELY(NodeInfoManager()->MathMLEnabled())) { 3338 styleSet.AppendStyleSheet(*cache->MathMLSheet()); 3339 } 3340 3341 if (MOZ_LIKELY(NodeInfoManager()->SVGEnabled())) { 3342 styleSet.AppendStyleSheet(*cache->SVGSheet()); 3343 } 3344 3345 styleSet.AppendStyleSheet(*cache->HTMLSheet()); 3346 3347 if (nsLayoutUtils::ShouldUseNoFramesSheet(this)) { 3348 styleSet.AppendStyleSheet(*cache->NoFramesSheet()); 3349 } 3350 3351 styleSet.AppendStyleSheet(*cache->CounterStylesSheet()); 3352 3353 // Only load the full XUL sheet if we'll need it. 3354 if (LoadsFullXULStyleSheetUpFront()) { 3355 styleSet.AppendStyleSheet(*cache->XULSheet()); 3356 } 3357 3358 styleSet.AppendStyleSheet(*cache->FormsSheet()); 3359 styleSet.AppendStyleSheet(*cache->ScrollbarsSheet()); 3360 3361 for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) { 3362 styleSet.AppendStyleSheet(*sheet); 3363 } 3364 3365 MOZ_ASSERT(!mQuirkSheetAdded); 3366 if (NeedsQuirksSheet()) { 3367 styleSet.AppendStyleSheet(*cache->QuirkSheet()); 3368 mQuirkSheetAdded = true; 3369 } 3370 } 3371 3372 void Document::FillStyleSet() { 3373 MOZ_ASSERT(!mStyleSetFilled); 3374 FillStyleSetUserAndUASheets(); 3375 FillStyleSetDocumentSheets(); 3376 mStyleSetFilled = true; 3377 } 3378 3379 void Document::FillStyleSetDocumentSheets() { 3380 ServoStyleSet& styleSet = EnsureStyleSet(); 3381 MOZ_ASSERT(styleSet.SheetCount(StyleOrigin::Author) == 0, 3382 "Style set already has document sheets?"); 3383 3384 // Sheets are added in reverse order to avoid worst-case time complexity when 3385 // looking up the index of a sheet. 3386 // 3387 // Note that usually appending is faster (rebuilds less stuff in the 3388 // styleset), but in this case it doesn't matter since we're filling the 3389 // styleset from scratch anyway. 3390 for (StyleSheet* sheet : Reversed(mStyleSheets)) { 3391 if (sheet->IsApplicable()) { 3392 styleSet.AddDocStyleSheet(*sheet); 3393 } 3394 } 3395 3396 EnumerateUniqueAdoptedStyleSheetsBackToFront([&](StyleSheet& aSheet) { 3397 if (aSheet.IsApplicable()) { 3398 styleSet.AddDocStyleSheet(aSheet); 3399 } 3400 }); 3401 3402 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); 3403 for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) { 3404 styleSet.AppendStyleSheet(*sheet); 3405 } 3406 3407 for (auto& sheets : mAdditionalSheets) { 3408 for (StyleSheet* sheet : sheets) { 3409 styleSet.AppendStyleSheet(*sheet); 3410 } 3411 } 3412 } 3413 3414 void Document::CompatibilityModeChanged() { 3415 MOZ_ASSERT(IsHTMLOrXHTML()); 3416 if (mCSSLoader) { 3417 mCSSLoader->SetCompatibilityMode(mCompatMode); 3418 } 3419 3420 if (mStyleSet) { 3421 mStyleSet->CompatibilityModeChanged(); 3422 } 3423 if (!mStyleSetFilled) { 3424 MOZ_ASSERT(!mQuirkSheetAdded); 3425 return; 3426 } 3427 3428 MOZ_ASSERT(mStyleSet); 3429 if (PresShell* presShell = GetPresShell()) { 3430 // Selectors may have become case-sensitive / case-insensitive, the stylist 3431 // has already performed the relevant invalidation. 3432 presShell->EnsureStyleFlush(); 3433 } 3434 if (mQuirkSheetAdded == NeedsQuirksSheet()) { 3435 return; 3436 } 3437 auto* cache = GlobalStyleSheetCache::Singleton(); 3438 StyleSheet* sheet = cache->QuirkSheet(); 3439 if (mQuirkSheetAdded) { 3440 mStyleSet->RemoveStyleSheet(*sheet); 3441 } else { 3442 mStyleSet->AppendStyleSheet(*sheet); 3443 } 3444 mQuirkSheetAdded = !mQuirkSheetAdded; 3445 ApplicableStylesChanged(); 3446 } 3447 3448 void Document::SetCompatibilityMode(nsCompatibility aMode) { 3449 NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards, 3450 "Bad compat mode for XHTML document!"); 3451 3452 if (mCompatMode == aMode) { 3453 return; 3454 } 3455 mCompatMode = aMode; 3456 CompatibilityModeChanged(); 3457 // Trigger recomputation of the nsViewportInfo the next time it's queried. 3458 mViewportType = Unknown; 3459 } 3460 3461 static void WarnIfSandboxIneffective(nsIDocShell* aDocShell, 3462 uint32_t aSandboxFlags, 3463 nsIChannel* aChannel) { 3464 // If the document permits allow-top-navigation and 3465 // allow-top-navigation-by-user-activation this will permit all top 3466 // navigation. 3467 if (aSandboxFlags != SANDBOXED_NONE && 3468 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) && 3469 !(aSandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION)) { 3470 nsContentUtils::ReportToConsole( 3471 nsIScriptError::warningFlag, "Iframe Sandbox"_ns, 3472 aDocShell->GetDocument(), nsContentUtils::eSECURITY_PROPERTIES, 3473 "BothAllowTopNavigationAndUserActivationPresent"); 3474 } 3475 // If the document is sandboxed (via the HTML5 iframe sandbox 3476 // attribute) and both the allow-scripts and allow-same-origin 3477 // keywords are supplied, the sandboxed document can call into its 3478 // parent document and remove its sandboxing entirely - we print a 3479 // warning to the web console in this case. 3480 if (aSandboxFlags & SANDBOXED_NAVIGATION && 3481 !(aSandboxFlags & SANDBOXED_SCRIPTS) && 3482 !(aSandboxFlags & SANDBOXED_ORIGIN)) { 3483 RefPtr<BrowsingContext> bc = aDocShell->GetBrowsingContext(); 3484 MOZ_ASSERT(bc->IsInProcess()); 3485 3486 RefPtr<BrowsingContext> parentBC = bc->GetParent(); 3487 if (!parentBC || !parentBC->IsInProcess()) { 3488 // If parent document is not in process, then by construction it 3489 // cannot be same origin. 3490 return; 3491 } 3492 3493 // Don't warn if our parent is not the top-level document. 3494 if (!parentBC->IsTopContent()) { 3495 return; 3496 } 3497 3498 nsCOMPtr<nsIDocShell> parentDocShell = parentBC->GetDocShell(); 3499 MOZ_ASSERT(parentDocShell); 3500 3501 nsCOMPtr<nsIChannel> parentChannel; 3502 parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel)); 3503 if (!parentChannel) { 3504 return; 3505 } 3506 nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel); 3507 if (NS_FAILED(rv)) { 3508 return; 3509 } 3510 3511 nsCOMPtr<Document> parentDocument = parentDocShell->GetDocument(); 3512 nsCOMPtr<nsIURI> iframeUri; 3513 parentChannel->GetURI(getter_AddRefs(iframeUri)); 3514 nsContentUtils::ReportToConsole( 3515 nsIScriptError::warningFlag, "Iframe Sandbox"_ns, parentDocument, 3516 nsContentUtils::eSECURITY_PROPERTIES, 3517 "BothAllowScriptsAndSameOriginPresent", nsTArray<nsString>(), 3518 SourceLocation(iframeUri.get())); 3519 } 3520 } 3521 3522 bool Document::IsSynthesized() { 3523 nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr; 3524 return loadInfo && loadInfo->GetServiceWorkerTaintingSynthesized(); 3525 } 3526 3527 // static 3528 bool Document::IsCallerChromeOrAddon(JSContext* aCx, JSObject* aObject) { 3529 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx); 3530 return principal && (principal->IsSystemPrincipal() || 3531 principal->GetIsAddonOrExpandedAddonPrincipal()); 3532 } 3533 3534 static void CheckIsBadPolicy(nsILoadInfo::CrossOriginOpenerPolicy aPolicy, 3535 BrowsingContext* aContext, nsIChannel* aChannel) { 3536 #if defined(EARLY_BETA_OR_EARLIER) 3537 auto requireCORP = 3538 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP; 3539 3540 if (aContext->GetOpenerPolicy() == aPolicy || 3541 (aContext->GetOpenerPolicy() != requireCORP && aPolicy != requireCORP)) { 3542 return; 3543 } 3544 3545 nsCOMPtr<nsIURI> uri; 3546 bool hasURI = NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(uri))); 3547 3548 bool isViewSource = hasURI && uri->SchemeIs("view-source"); 3549 3550 nsCString contentType; 3551 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel); 3552 bool isPDFJS = bag && 3553 NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns, 3554 contentType)) && 3555 contentType.EqualsLiteral(APPLICATION_PDF); 3556 3557 MOZ_DIAGNOSTIC_ASSERT(!isViewSource, 3558 "Bug 1834864: Assert due to view-source."); 3559 MOZ_DIAGNOSTIC_ASSERT(!isPDFJS, "Bug 1834864: Assert due to pdfjs."); 3560 MOZ_DIAGNOSTIC_ASSERT(aPolicy == requireCORP, 3561 "Assert due to clearing REQUIRE_CORP."); 3562 MOZ_DIAGNOSTIC_ASSERT(aContext->GetOpenerPolicy() == requireCORP, 3563 "Assert due to setting REQUIRE_CORP."); 3564 #endif // defined(EARLY_BETA_OR_EARLIER) 3565 } 3566 3567 void Document::ApplyCspFromLoadInfo(nsILoadInfo* aLoadInfo) { 3568 // The CSP directives upgrade-insecure-requests as well as 3569 // block-all-mixed-content not only apply to the toplevel document, 3570 // but also to nested documents. The loadInfo of a subdocument 3571 // load already holds the correct flag, so let's just set it here 3572 // on the document. Please note that we set the appropriate preload 3573 // bits just for the sake of completeness here, because the preloader 3574 // does not reach into subdocuments. 3575 mUpgradeInsecureRequests = aLoadInfo->GetUpgradeInsecureRequests(); 3576 mUpgradeInsecurePreloads = mUpgradeInsecureRequests; 3577 mBlockAllMixedContent = aLoadInfo->GetBlockAllMixedContent(); 3578 mBlockAllMixedContentPreloads = mBlockAllMixedContent; 3579 } 3580 3581 nsresult Document::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, 3582 nsILoadGroup* aLoadGroup, 3583 nsISupports* aContainer, 3584 nsIStreamListener** aDocListener, 3585 bool aReset) { 3586 if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) { 3587 nsCOMPtr<nsIURI> uri; 3588 aChannel->GetURI(getter_AddRefs(uri)); 3589 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, 3590 ("DOCUMENT %p StartDocumentLoad %s", this, 3591 uri ? uri->GetSpecOrDefault().get() : "")); 3592 } 3593 3594 MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED, 3595 "Bad readyState"); 3596 SetReadyStateInternal(READYSTATE_LOADING); 3597 3598 if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) { 3599 MOZ_RELEASE_ASSERT(mLoadedAsData); 3600 SetLoadedAsData(true, /* aConsiderForMemoryReporting */ true); 3601 // We need to disable script & style loading in this case. 3602 // We leave them disabled even in EndLoad(), and let anyone 3603 // who puts the document on display to worry about enabling. 3604 } else if (nsCRT::strcmp("external-resource", aCommand) == 0) { 3605 // Allow CSS, but not scripts 3606 // TODO: Enforce this via the constructor and make mScriptLoader null here. 3607 MOZ_ASSERT(mScriptLoader); 3608 mScriptLoader->SetEnabled(false); 3609 } 3610 3611 mMayStartLayout = false; 3612 MOZ_ASSERT(!mReadyForIdle, 3613 "We should never hit DOMContentLoaded before this point"); 3614 3615 if (aReset) { 3616 Reset(aChannel, aLoadGroup); 3617 } 3618 3619 nsAutoCString contentType; 3620 nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel); 3621 if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(u"contentType"_ns, 3622 contentType))) || 3623 NS_SUCCEEDED(aChannel->GetContentType(contentType))) { 3624 // XXX this is only necessary for viewsource: 3625 nsACString::const_iterator start, end, semicolon; 3626 contentType.BeginReading(start); 3627 contentType.EndReading(end); 3628 semicolon = start; 3629 FindCharInReadable(';', semicolon, end); 3630 SetContentType(Substring(start, semicolon)); 3631 } 3632 3633 RetrieveRelevantHeaders(aChannel); 3634 3635 mChannel = aChannel; 3636 RecomputeResistFingerprinting(); 3637 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel); 3638 if (inStrmChan) { 3639 bool isSrcdocChannel; 3640 inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel); 3641 if (isSrcdocChannel) { 3642 mIsSrcdocDocument = true; 3643 } 3644 } 3645 3646 if (mChannel) { 3647 nsLoadFlags loadFlags; 3648 mChannel->GetLoadFlags(&loadFlags); 3649 bool isDocument = false; 3650 mChannel->GetIsDocument(&isDocument); 3651 if (loadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE && isDocument && 3652 IsSynthesized() && XRE_IsContentProcess()) { 3653 ContentChild::UpdateCookieStatus(mChannel); 3654 } 3655 3656 // Store the security info for future use. 3657 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); 3658 } 3659 3660 // If this document is being loaded by a docshell, copy its sandbox flags 3661 // to the document, and store the fullscreen enabled flag. These are 3662 // immutable after being set here. 3663 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aContainer); 3664 3665 // If this is an error page, don't inherit sandbox flags 3666 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 3667 3668 if (!IsTopLevelContentDocument()) { 3669 SetAncestorOriginsList( 3670 ProduceAncestorOriginsList(loadInfo->AncestorPrincipals())); 3671 } 3672 3673 if (docShell && !loadInfo->GetLoadErrorPage()) { 3674 mSandboxFlags = loadInfo->GetSandboxFlags(); 3675 WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel()); 3676 } 3677 3678 nsCOMPtr<nsIClassifiedChannel> classifiedChannel = 3679 do_QueryInterface(aChannel); 3680 3681 if (classifiedChannel) { 3682 mClassificationFlags = { 3683 classifiedChannel->GetFirstPartyClassificationFlags(), 3684 classifiedChannel->GetThirdPartyClassificationFlags()}; 3685 } 3686 3687 // Set the opener policy for the top level content document. 3688 nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(mChannel); 3689 nsILoadInfo::CrossOriginOpenerPolicy policy = 3690 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; 3691 if (IsTopLevelContentDocument() && httpChan && 3692 NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy)) && docShell && 3693 docShell->GetBrowsingContext()) { 3694 CheckIsBadPolicy(policy, docShell->GetBrowsingContext(), aChannel); 3695 3696 // Setting the opener policy on a discarded context has no effect. 3697 (void)docShell->GetBrowsingContext()->SetOpenerPolicy(policy); 3698 } 3699 3700 ApplyCspFromLoadInfo(loadInfo); 3701 3702 // HTTPS-Only Mode flags 3703 // The HTTPS_ONLY_EXEMPT flag of the HTTPS-Only state gets propagated to all 3704 // sub-resources and sub-documents. 3705 mHttpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); 3706 3707 nsresult rv = InitReferrerInfo(aChannel); 3708 NS_ENSURE_SUCCESS(rv, rv); 3709 3710 rv = InitCOEP(aChannel); 3711 NS_ENSURE_SUCCESS(rv, rv); 3712 3713 // HACK: Calling EnsureIPCPoliciesRead() here will parse the CSP using the 3714 // context's current mSelfURI (which is still the previous mSelfURI), 3715 // bypassing some internal bugs with 'self' and iframe inheritance. 3716 // Not calling it here results in the mSelfURI being the current mSelfURI and 3717 // not the previous which breaks said inheritance. 3718 // https://bugzilla.mozilla.org/show_bug.cgi?id=1793560#ch-8 3719 nsCOMPtr<nsIPolicyContainer> policyContainer = 3720 loadInfo->GetPolicyContainerToInherit(); 3721 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = 3722 PolicyContainer::GetCSP(policyContainer); 3723 if (cspToInherit) { 3724 cspToInherit->EnsureIPCPoliciesRead(); 3725 } 3726 3727 rv = InitPolicyContainer(aChannel); 3728 NS_ENSURE_SUCCESS(rv, rv); 3729 3730 rv = InitCSP(aChannel); 3731 NS_ENSURE_SUCCESS(rv, rv); 3732 3733 rv = InitIntegrityPolicy(aChannel); 3734 NS_ENSURE_SUCCESS(rv, rv); 3735 3736 rv = InitDocPolicy(aChannel); 3737 NS_ENSURE_SUCCESS(rv, rv); 3738 3739 // Initialize FeaturePolicy 3740 rv = InitFeaturePolicy(aChannel); 3741 NS_ENSURE_SUCCESS(rv, rv); 3742 3743 rv = InitTLSCertificateBinding(aChannel); 3744 NS_ENSURE_SUCCESS(rv, rv); 3745 3746 rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings)); 3747 NS_ENSURE_SUCCESS(rv, rv); 3748 3749 MaybeRecomputePartitionKey(); 3750 3751 // Generally XFO and CSP frame-ancestors is handled within 3752 // DocumentLoadListener. However, the DocumentLoadListener can not handle 3753 // object and embed. Until then we have to enforce it here (See Bug 1646899). 3754 nsContentPolicyType internalContentType = 3755 loadInfo->InternalContentPolicyType(); 3756 if (internalContentType == nsIContentPolicy::TYPE_INTERNAL_OBJECT || 3757 internalContentType == nsIContentPolicy::TYPE_INTERNAL_EMBED) { 3758 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(aChannel); 3759 3760 nsresult status; 3761 aChannel->GetStatus(&status); 3762 if (status == NS_ERROR_XFO_VIOLATION) { 3763 // stop! ERROR page! 3764 // But before we have to reset the principal of the document 3765 // because the onload() event fires before the error page 3766 // is displayed and we do not want the enclosing document 3767 // to access the contentDocument. 3768 RefPtr<NullPrincipal> nullPrincipal = 3769 NullPrincipal::CreateWithInheritedAttributes(NodePrincipal()); 3770 // Before calling SetPrincipals() we should ensure that mFontFaceSet 3771 // and also GetInnerWindow() is still null at this point, before 3772 // we can fix Bug 1614735: Evaluate calls to SetPrincipal 3773 // within Document.cpp 3774 MOZ_ASSERT(!mFontFaceSet && !GetInnerWindow()); 3775 SetPrincipals(nullPrincipal, nullPrincipal); 3776 } 3777 } 3778 3779 return NS_OK; 3780 } 3781 3782 void Document::SetLoadedAsData(bool aLoadedAsData, 3783 bool aConsiderForMemoryReporting) { 3784 MOZ_RELEASE_ASSERT(aLoadedAsData == mLoadedAsData); 3785 if (aConsiderForMemoryReporting) { 3786 nsIGlobalObject* global = GetScopeObject(); 3787 if (global) { 3788 if (nsPIDOMWindowInner* window = global->GetAsInnerWindow()) { 3789 nsGlobalWindowInner::Cast(window) 3790 ->RegisterDataDocumentForMemoryReporting(this); 3791 } 3792 } 3793 } 3794 } 3795 3796 nsIContentSecurityPolicy* Document::GetPreloadCsp() const { 3797 return mPreloadCSP; 3798 } 3799 3800 void Document::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP) { 3801 mPreloadCSP = aPreloadCSP; 3802 } 3803 3804 void Document::GetCspJSON(nsString& aJSON) { 3805 aJSON.Truncate(); 3806 3807 nsIContentSecurityPolicy* csp = PolicyContainer::GetCSP(mPolicyContainer); 3808 if (!csp) { 3809 dom::CSPPolicies jsonPolicies; 3810 jsonPolicies.ToJSON(aJSON); 3811 return; 3812 } 3813 csp->ToJSON(aJSON); 3814 } 3815 3816 void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) { 3817 for (uint32_t i = 0; i < aMessages.Length(); ++i) { 3818 nsAutoString messageTag; 3819 aMessages[i]->GetTag(messageTag); 3820 3821 nsAutoString category; 3822 aMessages[i]->GetCategory(category); 3823 3824 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, 3825 NS_ConvertUTF16toUTF8(category), this, 3826 nsContentUtils::eSECURITY_PROPERTIES, 3827 NS_ConvertUTF16toUTF8(messageTag).get()); 3828 } 3829 } 3830 3831 void Document::ApplySettingsFromCSP(bool aSpeculative) { 3832 nsresult rv = NS_OK; 3833 if (!aSpeculative) { 3834 nsIContentSecurityPolicy* csp = PolicyContainer::GetCSP(mPolicyContainer); 3835 // 1) apply settings from regular CSP 3836 if (csp) { 3837 // Set up 'block-all-mixed-content' if not already inherited 3838 // from the parent context or set by any other CSP. 3839 if (!mBlockAllMixedContent) { 3840 bool block = false; 3841 rv = csp->GetBlockAllMixedContent(&block); 3842 NS_ENSURE_SUCCESS_VOID(rv); 3843 mBlockAllMixedContent = block; 3844 } 3845 if (!mBlockAllMixedContentPreloads) { 3846 mBlockAllMixedContentPreloads = mBlockAllMixedContent; 3847 } 3848 3849 // Set up 'upgrade-insecure-requests' if not already inherited 3850 // from the parent context or set by any other CSP. 3851 if (!mUpgradeInsecureRequests) { 3852 bool upgrade = false; 3853 rv = csp->GetUpgradeInsecureRequests(&upgrade); 3854 NS_ENSURE_SUCCESS_VOID(rv); 3855 mUpgradeInsecureRequests = upgrade; 3856 } 3857 if (!mUpgradeInsecurePreloads) { 3858 mUpgradeInsecurePreloads = mUpgradeInsecureRequests; 3859 } 3860 // Update csp settings in the parent process 3861 if (auto* wgc = GetWindowGlobalChild()) { 3862 wgc->SendUpdateDocumentCspSettings(mBlockAllMixedContent, 3863 mUpgradeInsecureRequests); 3864 } 3865 } 3866 return; 3867 } 3868 3869 // 2) apply settings from speculative csp 3870 if (mPreloadCSP) { 3871 if (!mBlockAllMixedContentPreloads) { 3872 bool block = false; 3873 rv = mPreloadCSP->GetBlockAllMixedContent(&block); 3874 NS_ENSURE_SUCCESS_VOID(rv); 3875 mBlockAllMixedContent = block; 3876 } 3877 if (!mUpgradeInsecurePreloads) { 3878 bool upgrade = false; 3879 rv = mPreloadCSP->GetUpgradeInsecureRequests(&upgrade); 3880 NS_ENSURE_SUCCESS_VOID(rv); 3881 mUpgradeInsecurePreloads = upgrade; 3882 } 3883 } 3884 } 3885 3886 nsresult Document::InitPolicyContainer(nsIChannel* aChannel) { 3887 bool shouldInherit = CSP_ShouldResponseInheritCSP(aChannel); 3888 if (shouldInherit) { 3889 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 3890 nsCOMPtr<nsIPolicyContainer> policyContainer = 3891 loadInfo->GetPolicyContainerToInherit(); 3892 mPolicyContainer = PolicyContainer::Cast(policyContainer); 3893 } 3894 3895 if (!mPolicyContainer) { 3896 mPolicyContainer = new PolicyContainer(); 3897 } 3898 3899 return NS_OK; 3900 } 3901 3902 void Document::SetPolicyContainer(nsIPolicyContainer* aPolicyContainer) { 3903 mPolicyContainer = PolicyContainer::Cast(aPolicyContainer); 3904 nsIContentSecurityPolicy* csp = PolicyContainer::GetCSP(mPolicyContainer); 3905 mHasPolicyWithRequireTrustedTypesForDirective = 3906 csp && csp->GetRequireTrustedTypesForDirectiveState() != 3907 RequireTrustedTypesForDirectiveState::NONE; 3908 } 3909 3910 nsIPolicyContainer* Document::GetPolicyContainer() const { 3911 return mPolicyContainer; 3912 } 3913 3914 nsresult Document::InitCSP(nsIChannel* aChannel) { 3915 MOZ_ASSERT(!mScriptGlobalObject, 3916 "CSP must be initialized before mScriptGlobalObject is set!"); 3917 MOZ_ASSERT(mPolicyContainer, 3918 "Policy container must be initialized before CSP!"); 3919 3920 // If this is a data document - no need to set CSP. 3921 if (mLoadedAsData) { 3922 return NS_OK; 3923 } 3924 3925 // If this is an image, no need to set a CSP. Otherwise SVG images 3926 // served with a CSP might block internally applied inline styles. 3927 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 3928 if (loadInfo->GetExternalContentPolicyType() == 3929 ExtContentPolicy::TYPE_IMAGE || 3930 loadInfo->GetExternalContentPolicyType() == 3931 ExtContentPolicy::TYPE_IMAGESET) { 3932 return NS_OK; 3933 } 3934 3935 nsIContentSecurityPolicy* csp = PolicyContainer::GetCSP(mPolicyContainer); 3936 bool inheritedCSP = !!csp; 3937 3938 // If there is no CSP to inherit, then we create a new CSP here so 3939 // that history entries always have the right reference in case a 3940 // Meta CSP gets dynamically added after the history entry has 3941 // already been created. 3942 if (!csp) { 3943 csp = new nsCSPContext(); 3944 mPolicyContainer->SetCSP(csp); 3945 mHasPolicyWithRequireTrustedTypesForDirective = false; 3946 } else { 3947 mHasPolicyWithRequireTrustedTypesForDirective = 3948 csp->GetRequireTrustedTypesForDirectiveState() != 3949 RequireTrustedTypesForDirectiveState::NONE; 3950 } 3951 3952 // Always overwrite the requesting context of the CSP so that any new 3953 // 'self' keyword added to an inherited CSP translates correctly. 3954 nsresult rv = csp->SetRequestContextWithDocument(this); 3955 if (NS_WARN_IF(NS_FAILED(rv))) { 3956 return rv; 3957 } 3958 3959 nsAutoCString tCspHeaderValue, tCspROHeaderValue; 3960 3961 nsCOMPtr<nsIHttpChannel> httpChannel; 3962 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); 3963 if (NS_WARN_IF(NS_FAILED(rv))) { 3964 return rv; 3965 } 3966 3967 if (httpChannel) { 3968 (void)httpChannel->GetResponseHeader("content-security-policy"_ns, 3969 tCspHeaderValue); 3970 3971 (void)httpChannel->GetResponseHeader( 3972 "content-security-policy-report-only"_ns, tCspROHeaderValue); 3973 } 3974 NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue); 3975 NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue); 3976 3977 // Check if this is a document from a WebExtension. 3978 nsCOMPtr<nsIPrincipal> principal = NodePrincipal(); 3979 MOZ_ASSERT(!BasePrincipal::Cast(principal)->Is<ExpandedPrincipal>()); 3980 auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy(); 3981 3982 // If there's no CSP to apply, go ahead and return early 3983 if (!inheritedCSP && !addonPolicy && cspHeaderValue.IsEmpty() && 3984 cspROHeaderValue.IsEmpty()) { 3985 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) { 3986 nsCOMPtr<nsIURI> chanURI; 3987 aChannel->GetURI(getter_AddRefs(chanURI)); 3988 nsAutoCString aspec; 3989 chanURI->GetAsciiSpec(aspec); 3990 MOZ_LOG(gCspPRLog, LogLevel::Debug, 3991 ("no CSP for document, %s", aspec.get())); 3992 } 3993 3994 return NS_OK; 3995 } 3996 3997 MOZ_LOG(gCspPRLog, LogLevel::Debug, 3998 ("Document is an add-on or CSP header specified %p", this)); 3999 4000 // ----- if the doc is an addon, apply its CSP. 4001 if (addonPolicy) { 4002 csp->AppendPolicy(addonPolicy->BaseCSP(), false, false); 4003 4004 csp->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false); 4005 } 4006 4007 // ----- if there's a full-strength CSP header, apply it. 4008 if (!cspHeaderValue.IsEmpty()) { 4009 mHasCSPDeliveredThroughHeader = true; 4010 rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false); 4011 NS_ENSURE_SUCCESS(rv, rv); 4012 } 4013 4014 // ----- if there's a report-only CSP header, apply it. 4015 if (!cspROHeaderValue.IsEmpty()) { 4016 rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true); 4017 NS_ENSURE_SUCCESS(rv, rv); 4018 } 4019 4020 // ----- Enforce sandbox policy if supplied in CSP header 4021 // The document may already have some sandbox flags set (e.g. if the document 4022 // is an iframe with the sandbox attribute set). If we have a CSP sandbox 4023 // directive, intersect the CSP sandbox flags with the existing flags. This 4024 // corresponds to the _least_ permissive policy. 4025 uint32_t cspSandboxFlags = SANDBOXED_NONE; 4026 rv = csp->GetCSPSandboxFlags(&cspSandboxFlags); 4027 NS_ENSURE_SUCCESS(rv, rv); 4028 4029 // Probably the iframe sandbox attribute already caused the creation of a 4030 // new NullPrincipal. Only create a new NullPrincipal if CSP requires so 4031 // and no one has been created yet. 4032 bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) && 4033 !(mSandboxFlags & SANDBOXED_ORIGIN); 4034 4035 mSandboxFlags |= cspSandboxFlags; 4036 4037 if (needNewNullPrincipal) { 4038 principal = NullPrincipal::CreateWithInheritedAttributes(principal); 4039 // Skip setting the content blocking allowlist principal to NullPrincipal. 4040 // The principal is only used to enable/disable trackingprotection via 4041 // permission and can be shared with the top level sandboxed site. 4042 // See Bug 1654546. 4043 SetPrincipals(principal, principal); 4044 } 4045 4046 ApplySettingsFromCSP(false); 4047 return NS_OK; 4048 } 4049 4050 nsresult Document::InitIntegrityPolicy(nsIChannel* aChannel) { 4051 MOZ_ASSERT(!mScriptGlobalObject, 4052 "Integrity Policy must be initialized before mScriptGlobalObject " 4053 "is set!"); 4054 MOZ_ASSERT(mPolicyContainer, 4055 "Policy container must be initialized before IntegrityPolicy!"); 4056 4057 if (mPolicyContainer->GetIntegrityPolicy()) { 4058 // We inherited the integrity policy. 4059 return NS_OK; 4060 } 4061 4062 nsAutoCString headerValue, headerROValue; 4063 nsCOMPtr<nsIHttpChannel> httpChannel; 4064 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); 4065 if (NS_WARN_IF(NS_FAILED(rv))) { 4066 return rv; 4067 } 4068 4069 if (httpChannel) { 4070 (void)httpChannel->GetResponseHeader("integrity-policy"_ns, headerValue); 4071 4072 (void)httpChannel->GetResponseHeader("integrity-policy-report-only"_ns, 4073 headerROValue); 4074 } 4075 4076 RefPtr<IntegrityPolicy> integrityPolicy; 4077 rv = IntegrityPolicy::ParseHeaders(headerValue, headerROValue, 4078 getter_AddRefs(integrityPolicy)); 4079 NS_ENSURE_SUCCESS(rv, rv); 4080 4081 mPolicyContainer->SetIntegrityPolicy(integrityPolicy); 4082 return NS_OK; 4083 } 4084 4085 nsresult Document::InitTLSCertificateBinding(nsIChannel* aChannel) { 4086 mTLSCertificateBindingURI = nullptr; 4087 nsCOMPtr<nsIHttpChannel> httpChannel; 4088 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); 4089 if (NS_WARN_IF(NS_FAILED(rv))) { 4090 return rv; 4091 } 4092 4093 if (!httpChannel) { 4094 return NS_OK; 4095 } 4096 4097 nsAutoCString linkHeader; 4098 rv = httpChannel->GetResponseHeader("link"_ns, linkHeader); 4099 if (NS_FAILED(rv) || linkHeader.IsEmpty()) { 4100 return NS_OK; 4101 } 4102 nsTArray<LinkHeader> linkHeaders( 4103 ParseLinkHeader(NS_ConvertUTF8toUTF16(linkHeader))); 4104 for (const auto& linkHeader : linkHeaders) { 4105 // According to ETSI TS 119 411-5 V2.1.1 Section 5.2, "When using a 2-QWAC, 4106 // website operators shall... Configure their website to serve... an HTTP 4107 // 'Link' response header (as defined in IETF RFC 8288 [6]) with a relative 4108 // reference to the TLS Certificate Binding, and a rel value of 4109 // tls-certificate-binding". 4110 if (linkHeader.mRel.EqualsIgnoreCase("tls-certificate-binding") && 4111 !net_IsAbsoluteURL(NS_ConvertUTF16toUTF8(linkHeader.mHref)) && 4112 !net_IsAbsoluteURL(NS_ConvertUTF16toUTF8(linkHeader.mAnchor))) { 4113 if (NS_SUCCEEDED(linkHeader.NewResolveHref( 4114 getter_AddRefs(mTLSCertificateBindingURI), mDocumentURI))) { 4115 break; 4116 } else { 4117 mTLSCertificateBindingURI = nullptr; 4118 } 4119 } 4120 } 4121 4122 return NS_OK; 4123 } 4124 4125 static FeaturePolicy* GetFeaturePolicyFromElement(Element* aElement) { 4126 if (auto* iframe = HTMLIFrameElement::FromNodeOrNull(aElement)) { 4127 return iframe->FeaturePolicy(); 4128 } 4129 4130 if (!HTMLObjectElement::FromNodeOrNull(aElement) && 4131 !HTMLEmbedElement::FromNodeOrNull(aElement)) { 4132 return nullptr; 4133 } 4134 4135 return aElement->OwnerDoc()->FeaturePolicy(); 4136 } 4137 4138 nsresult Document::InitDocPolicy(nsIChannel* aChannel) { 4139 // We only use document policy to implement the text fragments spec, so leave 4140 // everything at the default value if it isn't enabled. This includes the 4141 // behavior for element fragments. 4142 if (!StaticPrefs::dom_text_fragments_enabled()) { 4143 return NS_OK; 4144 } 4145 4146 nsCOMPtr<nsIHttpChannel> httpChannel; 4147 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); 4148 if (NS_WARN_IF(NS_FAILED(rv))) { 4149 return rv; 4150 } 4151 4152 nsAutoCString docPolicyString; 4153 if (httpChannel) { 4154 (void)httpChannel->GetResponseHeader("Document-Policy"_ns, docPolicyString); 4155 } 4156 4157 if (docPolicyString.IsEmpty()) { 4158 return NS_OK; 4159 } 4160 4161 mForceLoadAtTop = NS_GetForceLoadAtTopFromHeader(docPolicyString); 4162 4163 return NS_OK; 4164 } 4165 4166 void Document::InitFeaturePolicy( 4167 const Variant<Nothing, FeaturePolicyInfo, Element*>& 4168 aContainerFeaturePolicy) { 4169 MOZ_ASSERT(mFeaturePolicy, "we should have FeaturePolicy created"); 4170 4171 mFeaturePolicy->ResetDeclaredPolicy(); 4172 4173 mFeaturePolicy->SetDefaultOrigin(NodePrincipal()); 4174 4175 RefPtr<dom::FeaturePolicy> featurePolicy = mFeaturePolicy; 4176 aContainerFeaturePolicy.match( 4177 [](const Nothing&) {}, 4178 [featurePolicy](const FeaturePolicyInfo& aContainerFeaturePolicy) { 4179 // Let's inherit the policy from the possibly cross-origin container. 4180 featurePolicy->InheritPolicy(aContainerFeaturePolicy); 4181 featurePolicy->SetSrcOrigin(aContainerFeaturePolicy.mSrcOrigin); 4182 }, 4183 [featurePolicy](Element* aContainer) { 4184 // Let's inherit the policy from the parent container element if it 4185 // exists. 4186 if (RefPtr<dom::FeaturePolicy> containerFeaturePolicy = 4187 GetFeaturePolicyFromElement(aContainer)) { 4188 featurePolicy->InheritPolicy(containerFeaturePolicy); 4189 featurePolicy->SetSrcOrigin(containerFeaturePolicy->GetSrcOrigin()); 4190 } 4191 }); 4192 } 4193 4194 Element* GetEmbedderElementFrom(BrowsingContext* aBrowsingContext) { 4195 if (!aBrowsingContext) { 4196 return nullptr; 4197 } 4198 if (!aBrowsingContext->IsContentSubframe()) { 4199 return nullptr; 4200 } 4201 4202 return aBrowsingContext->GetEmbedderElement(); 4203 } 4204 4205 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) { 4206 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 4207 if (Element* embedderElement = GetEmbedderElementFrom(GetBrowsingContext())) { 4208 InitFeaturePolicy(AsVariant(embedderElement)); 4209 } else if (Maybe<FeaturePolicyInfo> featurePolicyContainer = 4210 loadInfo->GetContainerFeaturePolicyInfo()) { 4211 InitFeaturePolicy(AsVariant(*featurePolicyContainer)); 4212 } else { 4213 InitFeaturePolicy(AsVariant(Nothing{})); 4214 } 4215 4216 // We don't want to parse the http Feature-Policy header if this pref is off. 4217 if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) { 4218 return NS_OK; 4219 } 4220 4221 nsCOMPtr<nsIHttpChannel> httpChannel; 4222 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); 4223 if (NS_WARN_IF(NS_FAILED(rv))) { 4224 return rv; 4225 } 4226 4227 if (!httpChannel) { 4228 return NS_OK; 4229 } 4230 4231 // query the policy from the header 4232 nsAutoCString value; 4233 rv = httpChannel->GetResponseHeader("Feature-Policy"_ns, value); 4234 if (NS_SUCCEEDED(rv)) { 4235 mFeaturePolicy->SetDeclaredPolicy(this, NS_ConvertUTF8toUTF16(value), 4236 NodePrincipal(), nullptr); 4237 } 4238 4239 return NS_OK; 4240 } 4241 4242 void Document::EnsureNotEnteringAndExitFullscreen() { 4243 Document::ClearPendingFullscreenRequests(this); 4244 if (GetFullscreenElement()) { 4245 Document::AsyncExitFullscreen(this); 4246 } 4247 } 4248 4249 // https://html.spec.whatwg.org/#document-state-request-referrer-policy 4250 ReferrerPolicy Document::ReferrerPolicyUsedToFetchThisDocument() const { 4251 return mRequestReferrerPolicy; 4252 } 4253 4254 void Document::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) { 4255 mReferrerInfo = aReferrerInfo; 4256 mCachedReferrerInfoForInternalCSSAndSVGResources = nullptr; 4257 mCachedURLData = nullptr; 4258 } 4259 4260 nsresult Document::InitReferrerInfo(nsIChannel* aChannel) { 4261 MOZ_ASSERT(mReferrerInfo); 4262 MOZ_ASSERT(mPreloadReferrerInfo); 4263 4264 if (ReferrerInfo::ShouldResponseInheritReferrerInfo(aChannel)) { 4265 // The channel is loading `about:srcdoc`. Srcdoc loads should respond with 4266 // their parent's ReferrerInfo when asked for their ReferrerInfo, unless 4267 // they have an opaque origin. 4268 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer 4269 if (BrowsingContext* bc = GetBrowsingContext()) { 4270 // At this point the document is not fully created and mParentDocument has 4271 // not been set yet, 4272 Document* parentDoc = bc->GetEmbedderElement() 4273 ? bc->GetEmbedderElement()->OwnerDoc() 4274 : nullptr; 4275 if (parentDoc) { 4276 SetReferrerInfo(parentDoc->GetReferrerInfo()); 4277 mPreloadReferrerInfo = mReferrerInfo; 4278 return NS_OK; 4279 } 4280 4281 MOZ_ASSERT(bc->IsInProcess() || NodePrincipal()->GetIsNullPrincipal(), 4282 "srcdoc without null principal as toplevel!"); 4283 } 4284 } 4285 4286 nsCOMPtr<nsIHttpChannel> httpChannel; 4287 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); 4288 if (NS_WARN_IF(NS_FAILED(rv))) { 4289 return rv; 4290 } 4291 4292 if (!httpChannel) { 4293 return NS_OK; 4294 } 4295 4296 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo()) { 4297 SetReferrerInfo(referrerInfo); 4298 mRequestReferrerPolicy = referrerInfo->ReferrerPolicy(); 4299 } 4300 4301 // Override policy if we get one from Referrerr-Policy header 4302 mozilla::dom::ReferrerPolicy policy = 4303 nsContentUtils::GetReferrerPolicyFromChannel(aChannel); 4304 nsCOMPtr<nsIReferrerInfo> clone = 4305 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get()) 4306 ->CloneWithNewPolicy(policy); 4307 SetReferrerInfo(clone); 4308 mPreloadReferrerInfo = mReferrerInfo; 4309 return NS_OK; 4310 } 4311 4312 nsresult Document::InitCOEP(nsIChannel* aChannel) { 4313 nsCOMPtr<nsIHttpChannel> httpChannel; 4314 nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); 4315 if (NS_FAILED(rv)) { 4316 return NS_OK; 4317 } 4318 4319 nsCOMPtr<nsIHttpChannelInternal> intChannel = do_QueryInterface(httpChannel); 4320 4321 if (!intChannel) { 4322 return NS_OK; 4323 } 4324 4325 nsILoadInfo::CrossOriginEmbedderPolicy policy = 4326 nsILoadInfo::EMBEDDER_POLICY_NULL; 4327 if (NS_SUCCEEDED(intChannel->GetResponseEmbedderPolicy( 4328 mTrials.IsEnabled(OriginTrial::CoepCredentialless), &policy))) { 4329 mEmbedderPolicy = Some(policy); 4330 } 4331 4332 return NS_OK; 4333 } 4334 4335 void Document::StopDocumentLoad() { 4336 if (mParser) { 4337 mParserAborted = true; 4338 mParser->Terminate(); 4339 } 4340 } 4341 4342 void Document::SetDocumentURI(nsIURI* aURI) { 4343 nsCOMPtr<nsIURI> oldBase = GetDocBaseURI(); 4344 mDocumentURI = aURI; 4345 // This loosely implements §3.4.1 of Text Fragments 4346 // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives 4347 // Unlike specified in the spec, the fragment directive is not stripped from 4348 // the URL in the session history entry. Instead it is removed when the URL is 4349 // set in the `Document`. Also, instead of storing the `uninvokedDirective` in 4350 // `Document` as mentioned in the spec, the extracted directives are moved to 4351 // the `FragmentDirective` object which deals with finding the ranges to 4352 // highlight in `ScrollToRef()`. 4353 // XXX(:jjaschke): This is only a temporary solution. 4354 // https://bugzil.la/1881429 is filed for revisiting this. 4355 nsTArray<TextDirective> textDirectives; 4356 FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment( 4357 mDocumentURI, &textDirectives); 4358 if (!textDirectives.IsEmpty()) { 4359 SetUseCounter(eUseCounter_custom_TextDirectivePages); 4360 } 4361 FragmentDirective()->SetTextDirectives(std::move(textDirectives)); 4362 4363 nsIURI* newBase = GetDocBaseURI(); 4364 4365 mChromeRulesEnabled = URLExtraData::ChromeRulesEnabled(aURI); 4366 4367 bool equalBases = false; 4368 // Changing just the ref of a URI does not change how relative URIs would 4369 // resolve wrt to it, so we can treat the bases as equal as long as they're 4370 // equal ignoring the ref. 4371 if (oldBase && newBase) { 4372 oldBase->EqualsExceptRef(newBase, &equalBases); 4373 } else { 4374 equalBases = !oldBase && !newBase; 4375 } 4376 4377 // If this is the first time we're setting the document's URI, set the 4378 // document's original URI. 4379 if (!mOriginalURI) mOriginalURI = mDocumentURI; 4380 4381 // If changing the document's URI changed the base URI of the document, we 4382 // need to refresh the hrefs of all the links on the page. 4383 if (!equalBases) { 4384 mCachedURLData = nullptr; 4385 RefreshLinkHrefs(); 4386 } 4387 4388 // Tell our WindowGlobalParent that the document's URI has been changed. 4389 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) { 4390 wgc->SetDocumentURI(mDocumentURI); 4391 } 4392 } 4393 4394 static void GetFormattedTimeString(PRTime aTime, bool aUniversal, 4395 nsAString& aFormattedTimeString) { 4396 PRExplodedTime prtime; 4397 PR_ExplodeTime(aTime, aUniversal ? PR_GMTParameters : PR_LocalTimeParameters, 4398 &prtime); 4399 // "MM/DD/YYYY hh:mm:ss" 4400 char formatedTime[24]; 4401 if (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d", 4402 prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year), 4403 prtime.tm_hour, prtime.tm_min, prtime.tm_sec)) { 4404 CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString); 4405 } else { 4406 // If we for whatever reason failed to find the last modified time 4407 // (or even the current time), fall back to what NS4.x returned. 4408 aFormattedTimeString.AssignLiteral(u"01/01/1970 00:00:00"); 4409 } 4410 } 4411 4412 void Document::GetLastModified(nsAString& aLastModified) const { 4413 if (!mLastModified.IsEmpty()) { 4414 aLastModified.Assign(mLastModified); 4415 } else { 4416 GetFormattedTimeString(PR_Now(), 4417 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC), 4418 aLastModified); 4419 } 4420 } 4421 4422 static void IncrementExpandoGeneration(Document& aDoc) { 4423 ++aDoc.mExpandoAndGeneration.generation; 4424 } 4425 4426 void Document::AddToNameTable(Element* aElement, nsAtom* aName) { 4427 MOZ_ASSERT(nsGenericHTMLElement::ShouldExposeNameAsWindowProperty(aElement), 4428 "Only put elements that need to be exposed as window['name'] in " 4429 "the named table."); 4430 4431 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName); 4432 4433 // Null for out-of-memory 4434 if (entry) { 4435 if (!entry->HasNameElement() && 4436 !entry->HasIdElementExposedAsHTMLDocumentProperty()) { 4437 IncrementExpandoGeneration(*this); 4438 } 4439 entry->AddNameElement(this, aElement); 4440 } 4441 } 4442 4443 void Document::RemoveFromNameTable(Element* aElement, nsAtom* aName) { 4444 // Speed up document teardown 4445 if (mIdentifierMap.Count() == 0) return; 4446 4447 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName); 4448 if (!entry) // Could be false if the element was anonymous, hence never added 4449 return; 4450 4451 entry->RemoveNameElement(aElement); 4452 if (!entry->HasNameElement() && 4453 !entry->HasIdElementExposedAsHTMLDocumentProperty()) { 4454 IncrementExpandoGeneration(*this); 4455 } 4456 } 4457 4458 void Document::AddToDocumentNameTable(nsGenericHTMLElement* aElement, 4459 nsAtom* aName) { 4460 MOZ_ASSERT( 4461 nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) || 4462 nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty( 4463 aElement), 4464 "Only put elements that need to be exposed as document['name'] in " 4465 "the document named table."); 4466 4467 if (IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aName)) { 4468 entry->AddDocumentNameElement(this, aElement); 4469 } 4470 } 4471 4472 void Document::RemoveFromDocumentNameTable(nsGenericHTMLElement* aElement, 4473 nsAtom* aName) { 4474 if (mIdentifierMap.Count() == 0) { 4475 return; 4476 } 4477 4478 if (IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aName)) { 4479 entry->RemoveDocumentNameElement(aElement); 4480 nsBaseContentList* list = entry->GetDocumentNameContentList(); 4481 if (!list || list->Length() == 0) { 4482 IncrementExpandoGeneration(*this); 4483 } 4484 } 4485 } 4486 4487 void Document::AddToIdTable(Element* aElement, nsAtom* aId) { 4488 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId); 4489 4490 if (entry) { /* True except on OOM */ 4491 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) && 4492 !entry->HasNameElement() && 4493 !entry->HasIdElementExposedAsHTMLDocumentProperty()) { 4494 IncrementExpandoGeneration(*this); 4495 } 4496 entry->AddIdElement(aElement); 4497 } 4498 } 4499 4500 void Document::RemoveFromIdTable(Element* aElement, nsAtom* aId) { 4501 NS_ASSERTION(aId, "huhwhatnow?"); 4502 4503 // Speed up document teardown 4504 if (mIdentifierMap.Count() == 0) { 4505 return; 4506 } 4507 4508 IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId); 4509 if (!entry) // Can be null for XML elements with changing ids. 4510 return; 4511 4512 entry->RemoveIdElement(aElement); 4513 if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) && 4514 !entry->HasNameElement() && 4515 !entry->HasIdElementExposedAsHTMLDocumentProperty()) { 4516 IncrementExpandoGeneration(*this); 4517 } 4518 if (entry->IsEmpty()) { 4519 mIdentifierMap.RemoveEntry(entry); 4520 } 4521 } 4522 4523 void Document::UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer, 4524 bool aPreload) { 4525 ReferrerPolicyEnum policy = 4526 ReferrerInfo::ReferrerPolicyFromMetaString(aMetaReferrer); 4527 // The empty string "" corresponds to no referrer policy, causing a fallback 4528 // to a referrer policy defined elsewhere. 4529 if (policy == ReferrerPolicy::_empty) { 4530 return; 4531 } 4532 4533 MOZ_ASSERT(mReferrerInfo); 4534 MOZ_ASSERT(mPreloadReferrerInfo); 4535 4536 if (aPreload) { 4537 mPreloadReferrerInfo = 4538 static_cast<mozilla::dom::ReferrerInfo*>((mPreloadReferrerInfo).get()) 4539 ->CloneWithNewPolicy(policy); 4540 } else { 4541 nsCOMPtr<nsIReferrerInfo> clone = 4542 static_cast<mozilla::dom::ReferrerInfo*>((mReferrerInfo).get()) 4543 ->CloneWithNewPolicy(policy); 4544 SetReferrerInfo(clone); 4545 } 4546 } 4547 4548 void Document::SetPrincipals(nsIPrincipal* aNewPrincipal, 4549 nsIPrincipal* aNewPartitionedPrincipal) { 4550 MOZ_ASSERT(!!aNewPrincipal == !!aNewPartitionedPrincipal); 4551 if (aNewPrincipal && mAllowDNSPrefetch && 4552 StaticPrefs::network_dns_disablePrefetchFromHTTPS()) { 4553 if (aNewPrincipal->SchemeIs("https")) { 4554 mAllowDNSPrefetch = false; 4555 } 4556 } 4557 4558 if (mScriptLoader) { 4559 mScriptLoader->DeregisterFromCache(); 4560 } 4561 if (mCSSLoader) { 4562 mCSSLoader->DeregisterFromSheetCache(); 4563 } 4564 4565 mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal); 4566 mPartitionedPrincipal = aNewPartitionedPrincipal; 4567 4568 mCachedURLData = nullptr; 4569 4570 if (mCSSLoader) { 4571 mCSSLoader->RegisterInSheetCache(); 4572 } 4573 if (mScriptLoader) { 4574 mScriptLoader->RegisterToCache(); 4575 } 4576 4577 RecomputeResistFingerprinting(); 4578 4579 #ifdef DEBUG 4580 // Validate that the docgroup is set correctly. 4581 // 4582 // If we're setting the principal to null, we don't want to perform the check, 4583 // as the document is entering an intermediate state where it does not have a 4584 // principal. It will be given another real principal shortly which we will 4585 // check. It's not unsafe to have a document which has a null principal in the 4586 // same docgroup as another document, so this should not be a problem. 4587 if (aNewPrincipal) { 4588 AssertDocGroupMatchesKey(); 4589 } 4590 #endif 4591 } 4592 4593 #ifdef DEBUG 4594 void Document::AssertDocGroupMatchesKey() const { 4595 // Sanity check that we have an up-to-date and accurate docgroup 4596 // We only check if the principal when we can get the browsing context, as 4597 // documents without a BrowsingContext do not need to have a matching 4598 // principal to their DocGroup. 4599 4600 // Note that we can be invoked during cycle collection, so we need to handle 4601 // the browsingcontext being partially unlinked - normally you shouldn't 4602 // null-check `Group()` as it shouldn't return nullptr. 4603 if (!GetBrowsingContext() || !GetBrowsingContext()->Group()) { 4604 return; 4605 } 4606 4607 if (mDocGroup && mDocGroup->GetBrowsingContextGroup()) { 4608 MOZ_ASSERT(mDocGroup->GetBrowsingContextGroup() == 4609 GetBrowsingContext()->Group()); 4610 mDocGroup->AssertMatches(this); 4611 } 4612 } 4613 #endif 4614 4615 nsresult Document::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const { 4616 return SchedulerGroup::Dispatch(std::move(aRunnable)); 4617 } 4618 4619 void Document::NoteScriptTrackingStatus(const nsACString& aURL, 4620 net::ClassificationFlags& aFlags) { 4621 // If the script is not tracking, we don't need to do anything. 4622 if (aFlags.firstPartyFlags || aFlags.thirdPartyFlags) { 4623 mTrackingScripts.InsertOrUpdate(aURL, aFlags); 4624 } 4625 // Ideally, whether a given script is tracking or not should be consistent, 4626 // but there is a race so that it is not, when loading real sites in debug 4627 // builds. See bug 1925286. 4628 // MOZ_ASSERT_IF(!aIsTracking, !mTrackingScripts.Contains(aURL)); 4629 } 4630 4631 bool Document::IsScriptTracking(JSContext* aCx) const { 4632 JS::AutoFilename filename; 4633 if (!JS::DescribeScriptedCaller(&filename, aCx)) { 4634 return false; 4635 } 4636 4637 auto entry = mTrackingScripts.Lookup(nsDependentCString(filename.get())); 4638 if (!entry) { 4639 return false; 4640 } 4641 4642 return net::UrlClassifierCommon::IsTrackingClassificationFlag( 4643 entry.Data().thirdPartyFlags, IsInPrivateBrowsing()); 4644 } 4645 4646 net::ClassificationFlags Document::GetScriptTrackingFlags() const { 4647 if (auto loc = JSCallingLocation::Get()) { 4648 if (auto entry = mTrackingScripts.Lookup(loc.FileName())) { 4649 return entry.Data(); 4650 } 4651 } 4652 4653 // If the currently executing script is not a tracker, return the 4654 // classification flags of the document. 4655 4656 return mClassificationFlags; 4657 } 4658 4659 void Document::GetContentType(nsAString& aContentType) { 4660 CopyUTF8toUTF16(GetContentTypeInternal(), aContentType); 4661 } 4662 4663 void Document::SetContentType(const nsACString& aContentType) { 4664 if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None && 4665 aContentType.EqualsLiteral("application/xhtml+xml")) { 4666 mDefaultElementType = kNameSpaceID_XHTML; 4667 } 4668 4669 mCachedEncoder = nullptr; 4670 mContentType = aContentType; 4671 } 4672 4673 bool Document::HasPendingInitialTranslation() { 4674 return mDocumentL10n && mDocumentL10n->GetState() != DocumentL10nState::Ready; 4675 } 4676 4677 bool Document::HasPendingL10nMutations() const { 4678 return mDocumentL10n && mDocumentL10n->HasPendingMutations(); 4679 } 4680 4681 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) { 4682 JS::Rooted<JSObject*> object(aCx, aObject); 4683 nsCOMPtr<nsIPrincipal> callerPrincipal = 4684 nsContentUtils::SubjectPrincipal(aCx); 4685 nsGlobalWindowInner* win = xpc::WindowOrNull(object); 4686 bool allowed = false; 4687 callerPrincipal->IsL10nAllowed(win ? win->GetDocumentURI() : nullptr, 4688 &allowed); 4689 return allowed; 4690 } 4691 4692 void Document::LocalizationLinkAdded(Element* aLinkElement) { 4693 if (!AllowsL10n()) { 4694 return; 4695 } 4696 4697 nsAutoString href; 4698 aLinkElement->GetAttr(nsGkAtoms::href, href); 4699 4700 if (!mDocumentL10n) { 4701 Element* elem = GetDocumentElement(); 4702 MOZ_DIAGNOSTIC_ASSERT(elem); 4703 4704 bool isSync = elem->HasAttr(nsGkAtoms::datal10nsync); 4705 mDocumentL10n = DocumentL10n::Create(this, isSync); 4706 if (NS_WARN_IF(!mDocumentL10n)) { 4707 return; 4708 } 4709 } 4710 4711 mDocumentL10n->AddResourceId(NS_ConvertUTF16toUTF8(href)); 4712 4713 if (mReadyState >= READYSTATE_INTERACTIVE) { 4714 nsContentUtils::AddScriptRunner(NewRunnableMethod( 4715 "DocumentL10n::TriggerInitialTranslation()", mDocumentL10n, 4716 &DocumentL10n::TriggerInitialTranslation)); 4717 } else { 4718 if (!mDocumentL10n->mBlockingLayout) { 4719 // Our initial translation is going to block layout start. Make sure 4720 // we don't fire the load event until after that stops happening and 4721 // layout has a chance to start. 4722 BlockOnload(); 4723 mDocumentL10n->mBlockingLayout = true; 4724 } 4725 } 4726 } 4727 4728 void Document::LocalizationLinkRemoved(Element* aLinkElement) { 4729 if (!AllowsL10n()) { 4730 return; 4731 } 4732 4733 if (mDocumentL10n) { 4734 nsAutoString href; 4735 aLinkElement->GetAttr(nsGkAtoms::href, href); 4736 uint32_t remaining = 4737 mDocumentL10n->RemoveResourceId(NS_ConvertUTF16toUTF8(href)); 4738 if (remaining == 0) { 4739 if (mDocumentL10n->mBlockingLayout) { 4740 mDocumentL10n->mBlockingLayout = false; 4741 UnblockOnload(/* aFireSync = */ false); 4742 } 4743 mDocumentL10n = nullptr; 4744 } 4745 } 4746 } 4747 4748 /** 4749 * This method should be called once the end of the l10n 4750 * resource container has been parsed. 4751 * 4752 * In XUL this is the end of the first </linkset>, 4753 * In XHTML/HTML this is the end of </head>. 4754 * 4755 * This milestone is used to allow for batch 4756 * localization context I/O and building done 4757 * once when all resources in the document have been 4758 * collected. 4759 */ 4760 void Document::OnL10nResourceContainerParsed() { 4761 // XXX: This is a scaffolding for where we might inject prefetch 4762 // in bug 1717241. 4763 } 4764 4765 void Document::OnParsingCompleted() { 4766 // Let's call it again, in case the resource 4767 // container has not been closed, and only 4768 // now we're closing the document. 4769 OnL10nResourceContainerParsed(); 4770 4771 if (mDocumentL10n) { 4772 RefPtr<DocumentL10n> l10n = mDocumentL10n; 4773 l10n->TriggerInitialTranslation(); 4774 } 4775 } 4776 4777 void Document::InitialTranslationCompleted(bool aL10nCached) { 4778 if (mDocumentL10n && mDocumentL10n->mBlockingLayout) { 4779 // This means we blocked the load event in LocalizationLinkAdded. It's 4780 // important that the load blocker removal here be async, because our caller 4781 // will notify the content sink after us, and we want the content sync's 4782 // work to happen before the load event fires. 4783 mDocumentL10n->mBlockingLayout = false; 4784 UnblockOnload(/* aFireSync = */ false); 4785 } 4786 4787 mL10nProtoElements.Clear(); 4788 4789 nsXULPrototypeDocument* proto = GetPrototype(); 4790 if (proto) { 4791 proto->SetIsL10nCached(aL10nCached); 4792 } 4793 } 4794 4795 bool Document::AllowsL10n() const { 4796 if (IsStaticDocument()) { 4797 // We don't allow l10n on static documents, because the nodes are already 4798 // cloned translated, and static docs don't get parsed so we never 4799 // TriggerInitialTranslation, etc, so a load blocker would keep hanging 4800 // forever. 4801 return false; 4802 } 4803 bool allowed = false; 4804 NodePrincipal()->IsL10nAllowed(GetDocumentURI(), &allowed); 4805 return allowed; 4806 } 4807 4808 DocumentTimeline* Document::Timeline() { 4809 if (!mDocumentTimeline) { 4810 mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0)); 4811 } 4812 4813 return mDocumentTimeline; 4814 } 4815 4816 SVGSVGElement* Document::GetSVGRootElement() const { 4817 Element* root = GetRootElement(); 4818 if (!root || !root->IsSVGElement(nsGkAtoms::svg)) { 4819 return nullptr; 4820 } 4821 return static_cast<SVGSVGElement*>(root); 4822 } 4823 4824 /* Return true if the document is in the focused top-level window, and is an 4825 * ancestor of the focused DOMWindow. */ 4826 bool Document::HasFocus(ErrorResult& rv) const { 4827 nsFocusManager* fm = nsFocusManager::GetFocusManager(); 4828 if (!fm) { 4829 rv.Throw(NS_ERROR_NOT_AVAILABLE); 4830 return false; 4831 } 4832 4833 BrowsingContext* bc = GetBrowsingContext(); 4834 if (!bc) { 4835 return false; 4836 } 4837 4838 if (!fm->IsInActiveWindow(bc)) { 4839 return false; 4840 } 4841 4842 return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext()); 4843 } 4844 4845 bool Document::ThisDocumentHasFocus() const { 4846 nsFocusManager* fm = nsFocusManager::GetFocusManager(); 4847 return fm && fm->GetFocusedWindow() && 4848 fm->GetFocusedWindow()->GetExtantDoc() == this; 4849 } 4850 4851 void Document::GetDesignMode(nsAString& aDesignMode) { 4852 if (IsInDesignMode()) { 4853 aDesignMode.AssignLiteral("on"); 4854 } else { 4855 aDesignMode.AssignLiteral("off"); 4856 } 4857 } 4858 4859 void Document::SetDesignMode(const nsAString& aDesignMode, 4860 nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) { 4861 SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv); 4862 } 4863 4864 static void NotifyEditableStateChange(Document& aDoc) { 4865 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 4866 nsMutationGuard g; 4867 #endif 4868 for (nsIContent* node = aDoc.GetNextNode(&aDoc); node; 4869 node = node->GetNextNode(&aDoc)) { 4870 if (auto* element = Element::FromNode(node)) { 4871 element->UpdateEditableState(true); 4872 } 4873 } 4874 MOZ_DIAGNOSTIC_ASSERT(!g.Mutated(0)); 4875 } 4876 4877 void Document::SetDocumentEditableFlag(bool aEditable) { 4878 if (HasFlag(NODE_IS_EDITABLE) == aEditable) { 4879 return; 4880 } 4881 SetEditableFlag(aEditable); 4882 // Changing the NODE_IS_EDITABLE flags on document changes the intrinsic 4883 // state of all descendant elements of it. Update that now. 4884 NotifyEditableStateChange(*this); 4885 } 4886 4887 void Document::SetDesignMode(const nsAString& aDesignMode, 4888 const Maybe<nsIPrincipal*>& aSubjectPrincipal, 4889 ErrorResult& rv) { 4890 if (aSubjectPrincipal.isSome() && 4891 !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) { 4892 rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED); 4893 return; 4894 } 4895 const bool editableMode = IsInDesignMode(); 4896 if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) { 4897 SetDocumentEditableFlag(!editableMode); 4898 rv = EditingStateChanged(); 4899 } 4900 } 4901 4902 nsCommandManager* Document::GetMidasCommandManager() { 4903 // check if we have it cached 4904 if (mMidasCommandManager) { 4905 return mMidasCommandManager; 4906 } 4907 4908 nsPIDOMWindowOuter* window = GetWindow(); 4909 if (!window) { 4910 return nullptr; 4911 } 4912 4913 nsIDocShell* docshell = window->GetDocShell(); 4914 if (!docshell) { 4915 return nullptr; 4916 } 4917 4918 mMidasCommandManager = docshell->GetCommandManager(); 4919 return mMidasCommandManager; 4920 } 4921 4922 // static 4923 void Document::EnsureInitializeInternalCommandDataHashtable() { 4924 if (sInternalCommandDataHashtable) { 4925 return; 4926 } 4927 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor; 4928 sInternalCommandDataHashtable = new InternalCommandDataHashtable(); 4929 // clang-format off 4930 sInternalCommandDataHashtable->InsertOrUpdate( 4931 u"bold"_ns, 4932 InternalCommandData( 4933 "cmd_bold", 4934 Command::FormatBold, 4935 ExecCommandParam::Ignore, 4936 StyleUpdatingCommand::GetInstance, 4937 CommandOnTextEditor::Disabled)); 4938 sInternalCommandDataHashtable->InsertOrUpdate( 4939 u"italic"_ns, 4940 InternalCommandData( 4941 "cmd_italic", 4942 Command::FormatItalic, 4943 ExecCommandParam::Ignore, 4944 StyleUpdatingCommand::GetInstance, 4945 CommandOnTextEditor::Disabled)); 4946 sInternalCommandDataHashtable->InsertOrUpdate( 4947 u"underline"_ns, 4948 InternalCommandData( 4949 "cmd_underline", 4950 Command::FormatUnderline, 4951 ExecCommandParam::Ignore, 4952 StyleUpdatingCommand::GetInstance, 4953 CommandOnTextEditor::Disabled)); 4954 sInternalCommandDataHashtable->InsertOrUpdate( 4955 u"strikethrough"_ns, 4956 InternalCommandData( 4957 "cmd_strikethrough", 4958 Command::FormatStrikeThrough, 4959 ExecCommandParam::Ignore, 4960 StyleUpdatingCommand::GetInstance, 4961 CommandOnTextEditor::Disabled)); 4962 sInternalCommandDataHashtable->InsertOrUpdate( 4963 u"subscript"_ns, 4964 InternalCommandData( 4965 "cmd_subscript", 4966 Command::FormatSubscript, 4967 ExecCommandParam::Ignore, 4968 StyleUpdatingCommand::GetInstance, 4969 CommandOnTextEditor::Disabled)); 4970 sInternalCommandDataHashtable->InsertOrUpdate( 4971 u"superscript"_ns, 4972 InternalCommandData( 4973 "cmd_superscript", 4974 Command::FormatSuperscript, 4975 ExecCommandParam::Ignore, 4976 StyleUpdatingCommand::GetInstance, 4977 CommandOnTextEditor::Disabled)); 4978 sInternalCommandDataHashtable->InsertOrUpdate( 4979 u"cut"_ns, 4980 InternalCommandData( 4981 "cmd_cut", 4982 Command::Cut, 4983 ExecCommandParam::Ignore, 4984 CutCommand::GetInstance, 4985 CommandOnTextEditor::Enabled)); 4986 sInternalCommandDataHashtable->InsertOrUpdate( 4987 u"copy"_ns, 4988 InternalCommandData( 4989 "cmd_copy", 4990 Command::Copy, 4991 ExecCommandParam::Ignore, 4992 CopyCommand::GetInstance, 4993 CommandOnTextEditor::Enabled)); 4994 sInternalCommandDataHashtable->InsertOrUpdate( 4995 u"paste"_ns, 4996 InternalCommandData( 4997 "cmd_paste", 4998 Command::Paste, 4999 ExecCommandParam::Ignore, 5000 PasteCommand::GetInstance, 5001 CommandOnTextEditor::Enabled)); 5002 sInternalCommandDataHashtable->InsertOrUpdate( 5003 u"delete"_ns, 5004 InternalCommandData( 5005 "cmd_deleteCharBackward", 5006 Command::DeleteCharBackward, 5007 ExecCommandParam::Ignore, 5008 DeleteCommand::GetInstance, 5009 CommandOnTextEditor::Enabled)); 5010 sInternalCommandDataHashtable->InsertOrUpdate( 5011 u"forwarddelete"_ns, 5012 InternalCommandData( 5013 "cmd_deleteCharForward", 5014 Command::DeleteCharForward, 5015 ExecCommandParam::Ignore, 5016 DeleteCommand::GetInstance, 5017 CommandOnTextEditor::Enabled)); 5018 sInternalCommandDataHashtable->InsertOrUpdate( 5019 u"selectall"_ns, 5020 InternalCommandData( 5021 "cmd_selectAll", 5022 Command::SelectAll, 5023 ExecCommandParam::Ignore, 5024 SelectAllCommand::GetInstance, 5025 CommandOnTextEditor::Enabled)); 5026 sInternalCommandDataHashtable->InsertOrUpdate( 5027 u"undo"_ns, 5028 InternalCommandData( 5029 "cmd_undo", 5030 Command::HistoryUndo, 5031 ExecCommandParam::Ignore, 5032 UndoCommand::GetInstance, 5033 CommandOnTextEditor::Enabled)); 5034 sInternalCommandDataHashtable->InsertOrUpdate( 5035 u"redo"_ns, 5036 InternalCommandData( 5037 "cmd_redo", 5038 Command::HistoryRedo, 5039 ExecCommandParam::Ignore, 5040 RedoCommand::GetInstance, 5041 CommandOnTextEditor::Enabled)); 5042 sInternalCommandDataHashtable->InsertOrUpdate( 5043 u"indent"_ns, 5044 InternalCommandData("cmd_indent", 5045 Command::FormatIndent, 5046 ExecCommandParam::Ignore, 5047 IndentCommand::GetInstance, 5048 CommandOnTextEditor::Disabled)); 5049 sInternalCommandDataHashtable->InsertOrUpdate( 5050 u"outdent"_ns, 5051 InternalCommandData( 5052 "cmd_outdent", 5053 Command::FormatOutdent, 5054 ExecCommandParam::Ignore, 5055 OutdentCommand::GetInstance, 5056 CommandOnTextEditor::Disabled)); 5057 sInternalCommandDataHashtable->InsertOrUpdate( 5058 u"backcolor"_ns, 5059 InternalCommandData( 5060 "cmd_highlight", 5061 Command::FormatBackColor, 5062 ExecCommandParam::String, 5063 HighlightColorStateCommand::GetInstance, 5064 CommandOnTextEditor::Disabled)); 5065 sInternalCommandDataHashtable->InsertOrUpdate( 5066 u"hilitecolor"_ns, 5067 InternalCommandData( 5068 "cmd_highlight", 5069 Command::FormatBackColor, 5070 ExecCommandParam::String, 5071 HighlightColorStateCommand::GetInstance, 5072 CommandOnTextEditor::Disabled)); 5073 sInternalCommandDataHashtable->InsertOrUpdate( 5074 u"forecolor"_ns, 5075 InternalCommandData( 5076 "cmd_fontColor", 5077 Command::FormatFontColor, 5078 ExecCommandParam::String, 5079 FontColorStateCommand::GetInstance, 5080 CommandOnTextEditor::Disabled)); 5081 sInternalCommandDataHashtable->InsertOrUpdate( 5082 u"fontname"_ns, 5083 InternalCommandData( 5084 "cmd_fontFace", 5085 Command::FormatFontName, 5086 ExecCommandParam::String, 5087 FontFaceStateCommand::GetInstance, 5088 CommandOnTextEditor::Disabled)); 5089 sInternalCommandDataHashtable->InsertOrUpdate( 5090 u"fontsize"_ns, 5091 InternalCommandData( 5092 "cmd_fontSize", 5093 Command::FormatFontSize, 5094 ExecCommandParam::String, 5095 FontSizeStateCommand::GetInstance, 5096 CommandOnTextEditor::Disabled)); 5097 sInternalCommandDataHashtable->InsertOrUpdate( 5098 u"inserthorizontalrule"_ns, 5099 InternalCommandData( 5100 "cmd_insertHR", 5101 Command::InsertHorizontalRule, 5102 ExecCommandParam::Ignore, 5103 InsertTagCommand::GetInstance, 5104 CommandOnTextEditor::Disabled)); 5105 sInternalCommandDataHashtable->InsertOrUpdate( 5106 u"createlink"_ns, 5107 InternalCommandData( 5108 "cmd_insertLinkNoUI", 5109 Command::InsertLink, 5110 ExecCommandParam::String, 5111 InsertTagCommand::GetInstance, 5112 CommandOnTextEditor::Disabled)); 5113 sInternalCommandDataHashtable->InsertOrUpdate( 5114 u"insertimage"_ns, 5115 InternalCommandData( 5116 "cmd_insertImageNoUI", 5117 Command::InsertImage, 5118 ExecCommandParam::String, 5119 InsertTagCommand::GetInstance, 5120 CommandOnTextEditor::Disabled)); 5121 sInternalCommandDataHashtable->InsertOrUpdate( 5122 u"inserthtml"_ns, 5123 InternalCommandData( 5124 "cmd_insertHTML", 5125 Command::InsertHTML, 5126 ExecCommandParam::String, 5127 InsertHTMLCommand::GetInstance, 5128 // TODO: Chromium inserts text content of the document fragment 5129 // created from the param. 5130 // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/editing/commands/insert_commands.cc;l=105;drc=a4708b724062f17824815b896c3aaa43825128f8 5131 CommandOnTextEditor::Disabled)); 5132 sInternalCommandDataHashtable->InsertOrUpdate( 5133 u"inserttext"_ns, 5134 InternalCommandData( 5135 "cmd_insertText", 5136 Command::InsertText, 5137 ExecCommandParam::String, 5138 InsertPlaintextCommand::GetInstance, 5139 CommandOnTextEditor::Enabled)); 5140 sInternalCommandDataHashtable->InsertOrUpdate( 5141 u"justifyleft"_ns, 5142 InternalCommandData( 5143 "cmd_align", 5144 Command::FormatJustifyLeft, 5145 ExecCommandParam::Ignore, // Will be set to "left" 5146 AlignCommand::GetInstance, 5147 CommandOnTextEditor::Disabled)); 5148 sInternalCommandDataHashtable->InsertOrUpdate( 5149 u"justifyright"_ns, 5150 InternalCommandData( 5151 "cmd_align", 5152 Command::FormatJustifyRight, 5153 ExecCommandParam::Ignore, // Will be set to "right" 5154 AlignCommand::GetInstance, 5155 CommandOnTextEditor::Disabled)); 5156 sInternalCommandDataHashtable->InsertOrUpdate( 5157 u"justifycenter"_ns, 5158 InternalCommandData( 5159 "cmd_align", 5160 Command::FormatJustifyCenter, 5161 ExecCommandParam::Ignore, // Will be set to "center" 5162 AlignCommand::GetInstance, 5163 CommandOnTextEditor::Disabled)); 5164 sInternalCommandDataHashtable->InsertOrUpdate( 5165 u"justifyfull"_ns, 5166 InternalCommandData( 5167 "cmd_align", 5168 Command::FormatJustifyFull, 5169 ExecCommandParam::Ignore, // Will be set to "justify" 5170 AlignCommand::GetInstance, 5171 CommandOnTextEditor::Disabled)); 5172 sInternalCommandDataHashtable->InsertOrUpdate( 5173 u"removeformat"_ns, 5174 InternalCommandData( 5175 "cmd_removeStyles", 5176 Command::FormatRemove, 5177 ExecCommandParam::Ignore, 5178 RemoveStylesCommand::GetInstance, 5179 CommandOnTextEditor::Disabled)); 5180 sInternalCommandDataHashtable->InsertOrUpdate( 5181 u"unlink"_ns, 5182 InternalCommandData( 5183 "cmd_removeLinks", 5184 Command::FormatRemoveLink, 5185 ExecCommandParam::Ignore, 5186 StyleUpdatingCommand::GetInstance, 5187 CommandOnTextEditor::Disabled)); 5188 sInternalCommandDataHashtable->InsertOrUpdate( 5189 u"insertorderedlist"_ns, 5190 InternalCommandData( 5191 "cmd_ol", 5192 Command::InsertOrderedList, 5193 ExecCommandParam::Ignore, 5194 ListCommand::GetInstance, 5195 CommandOnTextEditor::Disabled)); 5196 sInternalCommandDataHashtable->InsertOrUpdate( 5197 u"insertunorderedlist"_ns, 5198 InternalCommandData( 5199 "cmd_ul", 5200 Command::InsertUnorderedList, 5201 ExecCommandParam::Ignore, 5202 ListCommand::GetInstance, 5203 CommandOnTextEditor::Disabled)); 5204 sInternalCommandDataHashtable->InsertOrUpdate( 5205 u"insertparagraph"_ns, 5206 InternalCommandData( 5207 "cmd_insertParagraph", 5208 Command::InsertParagraph, 5209 ExecCommandParam::Ignore, 5210 InsertParagraphCommand::GetInstance, 5211 CommandOnTextEditor::Enabled)); 5212 sInternalCommandDataHashtable->InsertOrUpdate( 5213 u"insertlinebreak"_ns, 5214 InternalCommandData( 5215 "cmd_insertLineBreak", 5216 Command::InsertLineBreak, 5217 ExecCommandParam::Ignore, 5218 InsertLineBreakCommand::GetInstance, 5219 CommandOnTextEditor::Enabled)); 5220 sInternalCommandDataHashtable->InsertOrUpdate( 5221 u"formatblock"_ns, 5222 InternalCommandData( 5223 "cmd_formatBlock", 5224 Command::FormatBlock, 5225 ExecCommandParam::String, 5226 FormatBlockStateCommand::GetInstance, 5227 CommandOnTextEditor::Disabled)); 5228 sInternalCommandDataHashtable->InsertOrUpdate( 5229 u"styleWithCSS"_ns, 5230 InternalCommandData( 5231 "cmd_setDocumentUseCSS", 5232 Command::SetDocumentUseCSS, 5233 ExecCommandParam::Boolean, 5234 SetDocumentStateCommand::GetInstance, 5235 CommandOnTextEditor::FallThrough)); 5236 sInternalCommandDataHashtable->InsertOrUpdate( 5237 u"usecss"_ns, // Legacy command 5238 InternalCommandData( 5239 "cmd_setDocumentUseCSS", 5240 Command::SetDocumentUseCSS, 5241 ExecCommandParam::InvertedBoolean, 5242 SetDocumentStateCommand::GetInstance, 5243 CommandOnTextEditor::FallThrough)); 5244 sInternalCommandDataHashtable->InsertOrUpdate( 5245 u"contentReadOnly"_ns, 5246 InternalCommandData( 5247 "cmd_setDocumentReadOnly", 5248 Command::SetDocumentReadOnly, 5249 ExecCommandParam::Boolean, 5250 SetDocumentStateCommand::GetInstance, 5251 CommandOnTextEditor::Enabled)); 5252 sInternalCommandDataHashtable->InsertOrUpdate( 5253 u"insertBrOnReturn"_ns, 5254 InternalCommandData( 5255 "cmd_insertBrOnReturn", 5256 Command::SetDocumentInsertBROnEnterKeyPress, 5257 ExecCommandParam::Boolean, 5258 SetDocumentStateCommand::GetInstance, 5259 CommandOnTextEditor::FallThrough)); 5260 sInternalCommandDataHashtable->InsertOrUpdate( 5261 u"defaultParagraphSeparator"_ns, 5262 InternalCommandData( 5263 "cmd_defaultParagraphSeparator", 5264 Command::SetDocumentDefaultParagraphSeparator, 5265 ExecCommandParam::String, 5266 SetDocumentStateCommand::GetInstance, 5267 CommandOnTextEditor::FallThrough)); 5268 sInternalCommandDataHashtable->InsertOrUpdate( 5269 u"enableObjectResizing"_ns, 5270 InternalCommandData( 5271 "cmd_enableObjectResizing", 5272 Command::ToggleObjectResizers, 5273 ExecCommandParam::Boolean, 5274 SetDocumentStateCommand::GetInstance, 5275 CommandOnTextEditor::FallThrough)); 5276 sInternalCommandDataHashtable->InsertOrUpdate( 5277 u"enableInlineTableEditing"_ns, 5278 InternalCommandData( 5279 "cmd_enableInlineTableEditing", 5280 Command::ToggleInlineTableEditor, 5281 ExecCommandParam::Boolean, 5282 SetDocumentStateCommand::GetInstance, 5283 CommandOnTextEditor::FallThrough)); 5284 sInternalCommandDataHashtable->InsertOrUpdate( 5285 u"enableAbsolutePositionEditing"_ns, 5286 InternalCommandData( 5287 "cmd_enableAbsolutePositionEditing", 5288 Command::ToggleAbsolutePositionEditor, 5289 ExecCommandParam::Boolean, 5290 SetDocumentStateCommand::GetInstance, 5291 CommandOnTextEditor::FallThrough)); 5292 sInternalCommandDataHashtable->InsertOrUpdate( 5293 u"enableCompatibleJoinSplitDirection"_ns, 5294 InternalCommandData("cmd_enableCompatibleJoinSplitNodeDirection", 5295 Command::EnableCompatibleJoinSplitNodeDirection, 5296 ExecCommandParam::Boolean, 5297 SetDocumentStateCommand::GetInstance, 5298 CommandOnTextEditor::FallThrough)); 5299 #if 0 5300 // with empty string 5301 sInternalCommandDataHashtable->InsertOrUpdate( 5302 u"justifynone"_ns, 5303 InternalCommandData( 5304 "cmd_align", 5305 Command::Undefined, 5306 ExecCommandParam::Ignore, 5307 nullptr, 5308 CommandOnTextEditor::Disabled)); // Not implemented yet. 5309 // REQUIRED SPECIAL REVIEW special review 5310 sInternalCommandDataHashtable->InsertOrUpdate( 5311 u"saveas"_ns, 5312 InternalCommandData( 5313 "cmd_saveAs", 5314 Command::Undefined, 5315 ExecCommandParam::Boolean, 5316 nullptr, 5317 CommandOnTextEditor::FallThrough)); // Not implemented yet. 5318 // REQUIRED SPECIAL REVIEW special review 5319 sInternalCommandDataHashtable->InsertOrUpdate( 5320 u"print"_ns, 5321 InternalCommandData( 5322 "cmd_print", 5323 Command::Undefined, 5324 ExecCommandParam::Boolean, 5325 nullptr, 5326 CommandOnTextEditor::FallThrough)); // Not implemented yet. 5327 #endif // #if 0 5328 // clang-format on 5329 } 5330 5331 Document::InternalCommandData Document::ConvertToInternalCommand( 5332 const nsAString& aHTMLCommandName, 5333 const TrustedHTMLOrString* aValue /* = nullptr */, 5334 nsIPrincipal* aSubjectPrincipal /* = nullptr */, 5335 ErrorResult* aRv /* = nullptr */, 5336 nsAString* aAdjustedValue /* = nullptr */) { 5337 MOZ_ASSERT(!aAdjustedValue || aAdjustedValue->IsEmpty()); 5338 EnsureInitializeInternalCommandDataHashtable(); 5339 InternalCommandData commandData; 5340 if (!sInternalCommandDataHashtable->Get(aHTMLCommandName, &commandData)) { 5341 return InternalCommandData(); 5342 } 5343 // Ignore if the command is disabled by a corresponding pref due to Gecko 5344 // specific. 5345 switch (commandData.mCommand) { 5346 case Command::SetDocumentReadOnly: 5347 if (!StaticPrefs::dom_document_edit_command_contentReadOnly_enabled() && 5348 aHTMLCommandName.LowerCaseEqualsLiteral("contentreadonly")) { 5349 return InternalCommandData(); 5350 } 5351 break; 5352 case Command::SetDocumentInsertBROnEnterKeyPress: 5353 MOZ_DIAGNOSTIC_ASSERT( 5354 aHTMLCommandName.LowerCaseEqualsLiteral("insertbronreturn")); 5355 if (!StaticPrefs::dom_document_edit_command_insertBrOnReturn_enabled()) { 5356 return InternalCommandData(); 5357 } 5358 break; 5359 default: 5360 break; 5361 } 5362 if (!aAdjustedValue) { 5363 // No further work to do 5364 return commandData; 5365 } 5366 MOZ_ASSERT(aValue); 5367 MOZ_ASSERT(aRv); 5368 Maybe<nsAutoString> compliantStringHolder; 5369 const nsAString* compliantString = nullptr; 5370 if (commandData.mCommand == Command::InsertHTML) { 5371 constexpr nsLiteralString sink = u"Document execCommand"_ns; 5372 compliantString = TrustedTypeUtils::GetTrustedTypesCompliantString( 5373 *aValue, sink, kTrustedTypesOnlySinkGroup, *this, aSubjectPrincipal, 5374 compliantStringHolder, *aRv); 5375 if (aRv->Failed()) { 5376 return InternalCommandData(); 5377 } 5378 } else { 5379 compliantString = aValue->IsString() ? &aValue->GetAsString() 5380 : &aValue->GetAsTrustedHTML().mData; 5381 } 5382 5383 switch (commandData.mExecCommandParam) { 5384 case ExecCommandParam::Ignore: 5385 // Just have to copy it, no checking 5386 switch (commandData.mCommand) { 5387 case Command::FormatJustifyLeft: 5388 aAdjustedValue->AssignLiteral("left"); 5389 break; 5390 case Command::FormatJustifyRight: 5391 aAdjustedValue->AssignLiteral("right"); 5392 break; 5393 case Command::FormatJustifyCenter: 5394 aAdjustedValue->AssignLiteral("center"); 5395 break; 5396 case Command::FormatJustifyFull: 5397 aAdjustedValue->AssignLiteral("justify"); 5398 break; 5399 default: 5400 MOZ_ASSERT(EditorCommand::GetParamType(commandData.mCommand) == 5401 EditorCommandParamType::None); 5402 break; 5403 } 5404 return commandData; 5405 5406 case ExecCommandParam::Boolean: 5407 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) & 5408 EditorCommandParamType::Bool)); 5409 // If this is a boolean value and it's not explicitly false (e.g. no 5410 // value). We default to "true" (see bug 301490). 5411 if (!compliantString->LowerCaseEqualsLiteral("false")) { 5412 aAdjustedValue->AssignLiteral("true"); 5413 } else { 5414 aAdjustedValue->AssignLiteral("false"); 5415 } 5416 return commandData; 5417 5418 case ExecCommandParam::InvertedBoolean: 5419 MOZ_ASSERT(!!(EditorCommand::GetParamType(commandData.mCommand) & 5420 EditorCommandParamType::Bool)); 5421 // For old backwards commands we invert the check. 5422 if (compliantString->LowerCaseEqualsLiteral("false")) { 5423 aAdjustedValue->AssignLiteral("true"); 5424 } else { 5425 aAdjustedValue->AssignLiteral("false"); 5426 } 5427 return commandData; 5428 5429 case ExecCommandParam::String: 5430 MOZ_ASSERT(!!( 5431 EditorCommand::GetParamType(commandData.mCommand) & 5432 (EditorCommandParamType::String | EditorCommandParamType::CString))); 5433 switch (commandData.mCommand) { 5434 case Command::FormatBlock: { 5435 const char16_t* start = compliantString->BeginReading(); 5436 const char16_t* end = compliantString->EndReading(); 5437 if (start != end && *start == '<' && *(end - 1) == '>') { 5438 ++start; 5439 --end; 5440 } 5441 // XXX Should we reorder this array with actual usage? 5442 static const nsStaticAtom* kFormattableBlockTags[] = { 5443 // clang-format off 5444 nsGkAtoms::address, 5445 nsGkAtoms::article, 5446 nsGkAtoms::aside, 5447 nsGkAtoms::blockquote, 5448 nsGkAtoms::dd, 5449 nsGkAtoms::div, 5450 nsGkAtoms::dl, 5451 nsGkAtoms::dt, 5452 nsGkAtoms::footer, 5453 nsGkAtoms::h1, 5454 nsGkAtoms::h2, 5455 nsGkAtoms::h3, 5456 nsGkAtoms::h4, 5457 nsGkAtoms::h5, 5458 nsGkAtoms::h6, 5459 nsGkAtoms::header, 5460 nsGkAtoms::hgroup, 5461 nsGkAtoms::main, 5462 nsGkAtoms::nav, 5463 nsGkAtoms::p, 5464 nsGkAtoms::pre, 5465 nsGkAtoms::section, 5466 // clang-format on 5467 }; 5468 nsAutoString value(nsDependentSubstring(start, end)); 5469 ToLowerCase(value); 5470 const nsStaticAtom* valueAtom = NS_GetStaticAtom(value); 5471 for (const nsStaticAtom* kTag : kFormattableBlockTags) { 5472 if (valueAtom == kTag) { 5473 kTag->ToString(*aAdjustedValue); 5474 return commandData; 5475 } 5476 } 5477 return InternalCommandData(); 5478 } 5479 case Command::FormatFontSize: { 5480 // Per editing spec as of April 23, 2012, we need to reject the value 5481 // if it's not a valid floating-point number surrounded by optional 5482 // whitespace. Otherwise, we parse it as a legacy font size. For 5483 // now, we just parse as a legacy font size regardless (matching 5484 // WebKit) -- bug 747879. 5485 int32_t size = nsContentUtils::ParseLegacyFontSize(*compliantString); 5486 if (!size) { 5487 return InternalCommandData(); 5488 } 5489 MOZ_ASSERT(aAdjustedValue->IsEmpty()); 5490 aAdjustedValue->AppendInt(size); 5491 return commandData; 5492 } 5493 case Command::InsertImage: 5494 case Command::InsertLink: 5495 if (compliantString->IsEmpty()) { 5496 // Invalid value, return false 5497 return InternalCommandData(); 5498 } 5499 aAdjustedValue->Assign(*compliantString); 5500 return commandData; 5501 case Command::SetDocumentDefaultParagraphSeparator: 5502 if (!compliantString->LowerCaseEqualsLiteral("div") && 5503 !compliantString->LowerCaseEqualsLiteral("p") && 5504 !compliantString->LowerCaseEqualsLiteral("br")) { 5505 // Invalid value 5506 return InternalCommandData(); 5507 } 5508 aAdjustedValue->Assign(*compliantString); 5509 return commandData; 5510 default: 5511 aAdjustedValue->Assign(*compliantString); 5512 return commandData; 5513 } 5514 5515 default: 5516 MOZ_ASSERT_UNREACHABLE("New ExecCommandParam value hasn't been handled"); 5517 return InternalCommandData(); 5518 } 5519 } 5520 5521 Document::AutoEditorCommandTarget::AutoEditorCommandTarget( 5522 Document& aDocument, const InternalCommandData& aCommandData) 5523 : mCommandData(aCommandData) { 5524 // We'll retrieve an editor with current DOM tree and layout information. 5525 // However, JS may have already hidden or remove exposed root content of 5526 // the editor. Therefore, we need the latest layout information here. 5527 aDocument.FlushPendingNotifications(FlushType::Layout); 5528 if (!aDocument.GetPresShell() || aDocument.GetPresShell()->IsDestroying()) { 5529 mDoNothing = true; 5530 return; 5531 } 5532 5533 if (nsPresContext* presContext = aDocument.GetPresContext()) { 5534 // Consider context of command handling which is automatically resolved 5535 // by order of controllers in `nsCommandManager::GetControllerForCommand()`. 5536 // The order is: 5537 // 1. TextEditor if there is an active element and it has TextEditor like 5538 // <input type="text"> or <textarea>. 5539 // 2. HTMLEditor for the document, if there is. 5540 // 3. Retarget to the DocShell or nsCommandManager as what we've done. 5541 if (aCommandData.IsCutOrCopyCommand()) { 5542 // Note that we used to use DocShell to handle `cut` and `copy` command 5543 // for dispatching corresponding events for making possible web apps to 5544 // implement their own editor without editable elements but supports 5545 // standard shortcut keys, etc. In this case, we prefer to use active 5546 // element's editor to keep same behavior. 5547 mActiveEditor = nsContentUtils::GetActiveEditor(presContext); 5548 } else { 5549 mActiveEditor = nsContentUtils::GetActiveEditor(presContext); 5550 mHTMLEditor = nsContentUtils::GetHTMLEditor(presContext); 5551 if (!mActiveEditor) { 5552 mActiveEditor = mHTMLEditor; 5553 } 5554 } 5555 } 5556 5557 // Then, retrieve editor command class instance which should handle it 5558 // and can handle it now. 5559 if (!mActiveEditor) { 5560 // If the command is available without editor, we should redirect the 5561 // command to focused descendant with DocShell. 5562 if (aCommandData.IsAvailableOnlyWhenEditable()) { 5563 mDoNothing = true; 5564 return; 5565 } 5566 return; 5567 } 5568 5569 // Otherwise, we should use EditorCommand instance (which is singleton 5570 // instance) when it's enabled. 5571 mEditorCommand = aCommandData.mGetEditorCommandFunc 5572 ? aCommandData.mGetEditorCommandFunc() 5573 : nullptr; 5574 if (!mEditorCommand) { 5575 mDoNothing = true; 5576 mActiveEditor = nullptr; 5577 mHTMLEditor = nullptr; 5578 return; 5579 } 5580 5581 if (IsCommandEnabled()) { 5582 return; 5583 } 5584 5585 // If the EditorCommand instance is disabled, we should do nothing if 5586 // the command requires an editor. 5587 if (aCommandData.IsAvailableOnlyWhenEditable()) { 5588 // Do nothing if editor specific commands is disabled (bug 760052). 5589 mDoNothing = true; 5590 return; 5591 } 5592 5593 // Otherwise, we should redirect it to focused descendant with DocShell. 5594 mEditorCommand = nullptr; 5595 mActiveEditor = nullptr; 5596 mHTMLEditor = nullptr; 5597 } 5598 5599 EditorBase* Document::AutoEditorCommandTarget::GetTargetEditor() const { 5600 using CommandOnTextEditor = InternalCommandData::CommandOnTextEditor; 5601 switch (mCommandData.mCommandOnTextEditor) { 5602 case CommandOnTextEditor::Enabled: 5603 return mActiveEditor; 5604 case CommandOnTextEditor::Disabled: 5605 return mActiveEditor && mActiveEditor->IsTextEditor() 5606 ? nullptr 5607 : mActiveEditor.get(); 5608 case CommandOnTextEditor::FallThrough: 5609 return mHTMLEditor; 5610 } 5611 return nullptr; 5612 } 5613 5614 bool Document::AutoEditorCommandTarget::IsEditable(Document* aDocument) const { 5615 if (RefPtr<Document> doc = aDocument->GetInProcessParentDocument()) { 5616 // Make sure frames are up to date, since that can affect whether 5617 // we're editable. 5618 doc->FlushPendingNotifications(FlushType::Frames); 5619 } 5620 EditorBase* targetEditor = GetTargetEditor(); 5621 if (targetEditor && targetEditor->IsTextEditor()) { 5622 // FYI: When `disabled` attribute is set, `TextEditor` treats it as 5623 // "readonly" too. 5624 return !targetEditor->IsReadonly(); 5625 } 5626 return aDocument->IsEditingOn(); 5627 } 5628 5629 bool Document::AutoEditorCommandTarget::IsCommandEnabled() const { 5630 EditorBase* targetEditor = GetTargetEditor(); 5631 if (!targetEditor) { 5632 return false; 5633 } 5634 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor); 5635 return MOZ_KnownLive(mEditorCommand) 5636 ->IsCommandEnabled(mCommandData.mCommand, MOZ_KnownLive(targetEditor)); 5637 } 5638 5639 nsresult Document::AutoEditorCommandTarget::DoCommand( 5640 nsIPrincipal* aPrincipal) const { 5641 MOZ_ASSERT(!DoNothing()); 5642 MOZ_ASSERT(mEditorCommand); 5643 EditorBase* targetEditor = GetTargetEditor(); 5644 if (!targetEditor) { 5645 return NS_SUCCESS_DOM_NO_OPERATION; 5646 } 5647 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor); 5648 return MOZ_KnownLive(mEditorCommand) 5649 ->DoCommand(mCommandData.mCommand, MOZ_KnownLive(*targetEditor), 5650 aPrincipal); 5651 } 5652 5653 template <typename ParamType> 5654 nsresult Document::AutoEditorCommandTarget::DoCommandParam( 5655 const ParamType& aParam, nsIPrincipal* aPrincipal) const { 5656 MOZ_ASSERT(!DoNothing()); 5657 MOZ_ASSERT(mEditorCommand); 5658 EditorBase* targetEditor = GetTargetEditor(); 5659 if (!targetEditor) { 5660 return NS_SUCCESS_DOM_NO_OPERATION; 5661 } 5662 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor); 5663 return MOZ_KnownLive(mEditorCommand) 5664 ->DoCommandParam(mCommandData.mCommand, aParam, 5665 MOZ_KnownLive(*targetEditor), aPrincipal); 5666 } 5667 5668 nsresult Document::AutoEditorCommandTarget::GetCommandStateParams( 5669 nsCommandParams& aParams) const { 5670 MOZ_ASSERT(mEditorCommand); 5671 EditorBase* targetEditor = GetTargetEditor(); 5672 if (!targetEditor) { 5673 return NS_OK; 5674 } 5675 MOZ_ASSERT(targetEditor == mActiveEditor || targetEditor == mHTMLEditor); 5676 return MOZ_KnownLive(mEditorCommand) 5677 ->GetCommandStateParams(mCommandData.mCommand, MOZ_KnownLive(aParams), 5678 MOZ_KnownLive(targetEditor), nullptr); 5679 } 5680 5681 Document::AutoRunningExecCommandMarker::AutoRunningExecCommandMarker( 5682 Document& aDocument, nsIPrincipal* aPrincipal) 5683 : mDocument(aDocument), 5684 mTreatAsUserInput(EditorBase::TreatAsUserInput(aPrincipal)), 5685 mHasBeenRunningByContent(aDocument.mIsRunningExecCommandByContent), 5686 mHasBeenRunningByChromeOrAddon( 5687 aDocument.mIsRunningExecCommandByChromeOrAddon) { 5688 if (mTreatAsUserInput) { 5689 aDocument.mIsRunningExecCommandByChromeOrAddon = true; 5690 } else { 5691 aDocument.mIsRunningExecCommandByContent = true; 5692 } 5693 } 5694 5695 /** 5696 * Returns true if calling execCommand with 'paste' arguments is allowed for the 5697 * given subject principal. These are only allowed if the user initiated them 5698 * (like with a mouse-click or key press). 5699 */ 5700 static bool IsExecCommandPasteAllowed(Document* aDocument, 5701 nsIPrincipal& aSubjectPrincipal) { 5702 if (StaticPrefs::dom_execCommand_paste_enabled() && aDocument && 5703 aDocument->HasValidTransientUserGestureActivation()) { 5704 return true; 5705 } 5706 5707 return nsContentUtils::PrincipalHasPermission(aSubjectPrincipal, 5708 nsGkAtoms::clipboardRead); 5709 } 5710 5711 bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI, 5712 const TrustedHTMLOrString& aValue, 5713 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 5714 // Only allow on HTML documents. 5715 if (!IsHTMLOrXHTML()) { 5716 aRv.ThrowInvalidStateError( 5717 "execCommand is only supported on HTML documents"); 5718 return false; 5719 } 5720 // Otherwise, don't throw exception for compatibility with Chrome. 5721 5722 // if they are requesting UI from us, let's fail since we have no UI 5723 if (aShowUI) { 5724 return false; 5725 } 5726 5727 // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go() 5728 // this might add some ugly JS dependencies? 5729 5730 nsAutoString adjustedValue; 5731 InternalCommandData commandData = ConvertToInternalCommand( 5732 aHTMLCommandName, &aValue, &aSubjectPrincipal, &aRv, &adjustedValue); 5733 switch (commandData.mCommand) { 5734 case Command::DoNothing: 5735 return false; 5736 case Command::SetDocumentReadOnly: 5737 SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly); 5738 break; 5739 case Command::EnableCompatibleJoinSplitNodeDirection: 5740 // We didn't allow to enable the legacy behavior once we've enabled the 5741 // new behavior by default. For keeping the behavior at supporting both 5742 // mode, we should keep returning `false` if the web app to enable the 5743 // legacy mode. Additionally, we don't support the legacy direction 5744 // anymore. Therefore, we can return `false` here even if the caller is 5745 // an addon or chrome script. 5746 if (!adjustedValue.EqualsLiteral("true")) { 5747 return false; 5748 } 5749 break; 5750 default: 5751 break; 5752 } 5753 5754 AutoRunningExecCommandMarker markRunningExecCommand(*this, 5755 &aSubjectPrincipal); 5756 5757 // If we're running an execCommand, we should just return false. 5758 // https://github.com/w3c/editing/issues/200#issuecomment-575241816 5759 if (!markRunningExecCommand.IsSafeToRun()) { 5760 return false; 5761 } 5762 5763 // Do security check first. 5764 if (commandData.IsCutOrCopyCommand()) { 5765 if (!nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal)) { 5766 // We have rejected the event due to it not being performed in an 5767 // input-driven context therefore, we report the error to the console. 5768 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, 5769 this, nsContentUtils::eDOM_PROPERTIES, 5770 "ExecCommandCutCopyDeniedNotInputDriven"); 5771 return false; 5772 } 5773 } else if (commandData.IsPasteCommand()) { 5774 if (!IsExecCommandPasteAllowed(this, aSubjectPrincipal)) { 5775 if (StaticPrefs::dom_execCommand_paste_enabled()) { 5776 // We rejected the command because it was not performed with a valid 5777 // user activation; therefore, we report the error to the console. 5778 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, 5779 this, nsContentUtils::eDOM_PROPERTIES, 5780 "ExecCommandPasteDeniedNotInputDriven"); 5781 } 5782 return false; 5783 } 5784 } 5785 5786 // Next, consider context of command handling which is automatically resolved 5787 // by order of controllers in `nsCommandManager::GetControllerForCommand()`. 5788 AutoEditorCommandTarget editCommandTarget(*this, commandData); 5789 if (commandData.IsAvailableOnlyWhenEditable()) { 5790 if (!editCommandTarget.IsEditable(this)) { 5791 return false; 5792 } 5793 // If currently the editor cannot dispatch `input` events, it means that the 5794 // editor value is being set and that caused unexpected composition events. 5795 // In this case, the value will be updated to the setting value soon and 5796 // Chromium does not dispatch any events during the sequence but we dispatch 5797 // `compositionupdate` and `compositionend` events to conform to the UI 5798 // Events spec. Therefore, this execCommand must be called accidentally. 5799 EditorBase* targetEditor = editCommandTarget.GetTargetEditor(); 5800 if (targetEditor && targetEditor->IsSuppressingDispatchingInputEvent()) { 5801 return false; 5802 } 5803 } 5804 5805 if (editCommandTarget.DoNothing()) { 5806 return false; 5807 } 5808 5809 // If we cannot use EditorCommand instance directly, we need to handle the 5810 // command with traditional path (i.e., with DocShell or nsCommandManager). 5811 if (!editCommandTarget.IsEditor()) { 5812 MOZ_ASSERT(!commandData.IsAvailableOnlyWhenEditable()); 5813 5814 // Special case clipboard write commands like Command::Cut and 5815 // Command::Copy. For such commands, we need the behaviour from 5816 // nsWindowRoot::GetControllers() which is to look at the focused element, 5817 // and defer to a focused textbox's controller. The code past taken by 5818 // other commands in ExecCommand() always uses the window directly, rather 5819 // than deferring to the textbox, which is desireable for most editor 5820 // commands, but not these commands (as those should allow copying out of 5821 // embedded editors). This behaviour is invoked if we call DoCommand() 5822 // directly on the docShell. 5823 // XXX This means that we allow web app to pick up selected content in 5824 // descendant document and write it into the clipboard when a 5825 // descendant document has focus. However, Chromium does not allow 5826 // this and this seems that it's not good behavior from point of view 5827 // of security. We should treat this issue in another bug. 5828 if (commandData.IsCutOrCopyCommand()) { 5829 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer); 5830 if (!docShell) { 5831 return false; 5832 } 5833 nsresult rv = docShell->DoCommand(commandData.mXULCommandName); 5834 if (rv == NS_SUCCESS_DOM_NO_OPERATION) { 5835 return false; 5836 } 5837 return NS_SUCCEEDED(rv); 5838 } 5839 5840 // Otherwise (currently, only clipboard read commands like Command::Paste), 5841 // we don't need to redirect the command to focused subdocument. 5842 // Therefore, we should handle it with nsCommandManager as used to be. 5843 // It may dispatch only preceding event of editing on non-editable element 5844 // to make web apps possible to handle standard shortcut key, etc in 5845 // their own editor. 5846 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager(); 5847 if (!commandManager) { 5848 return false; 5849 } 5850 5851 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow(); 5852 if (!window) { 5853 return false; 5854 } 5855 5856 // Return false for disabled commands (bug 760052) 5857 if (!commandManager->IsCommandEnabled( 5858 nsDependentCString(commandData.mXULCommandName), window)) { 5859 return false; 5860 } 5861 5862 MOZ_ASSERT(commandData.IsPasteCommand() || 5863 commandData.mCommand == Command::SelectAll); 5864 nsresult rv = 5865 commandManager->DoCommand(commandData.mXULCommandName, nullptr, window); 5866 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION; 5867 } 5868 5869 // Now, our target is fixed to the editor. So, we can use EditorCommand 5870 // in EditorCommandTarget directly. 5871 5872 EditorCommandParamType paramType = 5873 EditorCommand::GetParamType(commandData.mCommand); 5874 5875 // If we don't have meaningful parameter or the EditorCommand does not 5876 // require additional parameter, we can use `DoCommand()`. 5877 if (adjustedValue.IsEmpty() || paramType == EditorCommandParamType::None) { 5878 MOZ_ASSERT(!(paramType & EditorCommandParamType::Bool)); 5879 nsresult rv = editCommandTarget.DoCommand(&aSubjectPrincipal); 5880 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION; 5881 } 5882 5883 // If the EditorCommand requires `bool` parameter, `adjustedValue` must be 5884 // "true" or "false" here. So, we can use `DoCommandParam()` which takes 5885 // a `bool` value. 5886 if (!!(paramType & EditorCommandParamType::Bool)) { 5887 MOZ_ASSERT(adjustedValue.EqualsLiteral("true") || 5888 adjustedValue.EqualsLiteral("false")); 5889 nsresult rv = editCommandTarget.DoCommandParam( 5890 Some(adjustedValue.EqualsLiteral("true")), &aSubjectPrincipal); 5891 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION; 5892 } 5893 5894 // Now, the EditorCommand requires `nsAString` or `nsACString` parameter 5895 // in this case. However, `paramType` may contain both `String` and 5896 // `CString` but in such case, we should use `DoCommandParam()` which 5897 // takes `nsAString`. So, we should check whether `paramType` contains 5898 // `String` or not first. 5899 if (!!(paramType & EditorCommandParamType::String)) { 5900 MOZ_ASSERT(!adjustedValue.IsVoid()); 5901 nsresult rv = 5902 editCommandTarget.DoCommandParam(adjustedValue, &aSubjectPrincipal); 5903 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION; 5904 } 5905 5906 // Finally, `paramType` should have `CString`. We should use 5907 // `DoCommandParam()` which takes `nsACString`. 5908 if (!!(paramType & EditorCommandParamType::CString)) { 5909 NS_ConvertUTF16toUTF8 utf8Value(adjustedValue); 5910 MOZ_ASSERT(!utf8Value.IsVoid()); 5911 nsresult rv = 5912 editCommandTarget.DoCommandParam(utf8Value, &aSubjectPrincipal); 5913 return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION; 5914 } 5915 5916 MOZ_ASSERT_UNREACHABLE( 5917 "Not yet implemented to handle new EditorCommandParamType"); 5918 return false; 5919 } 5920 5921 bool Document::QueryCommandEnabled(const nsAString& aHTMLCommandName, 5922 nsIPrincipal& aSubjectPrincipal, 5923 ErrorResult& aRv) { 5924 // Only allow on HTML documents. 5925 if (!IsHTMLOrXHTML()) { 5926 aRv.ThrowInvalidStateError( 5927 "queryCommandEnabled is only supported on HTML documents"); 5928 return false; 5929 } 5930 // Otherwise, don't throw exception for compatibility with Chrome. 5931 5932 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName); 5933 switch (commandData.mCommand) { 5934 case Command::DoNothing: 5935 return false; 5936 case Command::SetDocumentReadOnly: 5937 SetUseCounter( 5938 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly); 5939 break; 5940 case Command::SetDocumentInsertBROnEnterKeyPress: 5941 SetUseCounter( 5942 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn); 5943 break; 5944 default: 5945 break; 5946 } 5947 5948 // Report false for restricted commands 5949 if (commandData.IsCutOrCopyCommand()) { 5950 // XXX: should we report "disabled" when the target is not editable for cut 5951 // command? 5952 return nsContentUtils::IsCutCopyAllowed(this, aSubjectPrincipal); 5953 } 5954 5955 if (commandData.IsPasteCommand() && 5956 !IsExecCommandPasteAllowed(this, aSubjectPrincipal)) { 5957 return false; 5958 } 5959 5960 AutoEditorCommandTarget editCommandTarget(*this, commandData); 5961 if (commandData.IsAvailableOnlyWhenEditable() && 5962 !editCommandTarget.IsEditable(this)) { 5963 return false; 5964 } 5965 5966 if (editCommandTarget.IsEditor()) { 5967 return editCommandTarget.IsCommandEnabled(); 5968 } 5969 5970 // get command manager and dispatch command to our window if it's acceptable 5971 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager(); 5972 if (!commandManager) { 5973 return false; 5974 } 5975 5976 nsPIDOMWindowOuter* window = GetWindow(); 5977 if (!window) { 5978 return false; 5979 } 5980 5981 return commandManager->IsCommandEnabled( 5982 nsDependentCString(commandData.mXULCommandName), window); 5983 } 5984 5985 bool Document::QueryCommandIndeterm(const nsAString& aHTMLCommandName, 5986 ErrorResult& aRv) { 5987 // Only allow on HTML documents. 5988 if (!IsHTMLOrXHTML()) { 5989 aRv.ThrowInvalidStateError( 5990 "queryCommandIndeterm is only supported on HTML documents"); 5991 return false; 5992 } 5993 // Otherwise, don't throw exception for compatibility with Chrome. 5994 5995 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName); 5996 if (commandData.mCommand == Command::DoNothing) { 5997 return false; 5998 } 5999 6000 AutoEditorCommandTarget editCommandTarget(*this, commandData); 6001 if (commandData.IsAvailableOnlyWhenEditable() && 6002 !editCommandTarget.IsEditable(this)) { 6003 return false; 6004 } 6005 RefPtr<nsCommandParams> params = new nsCommandParams(); 6006 if (editCommandTarget.IsEditor()) { 6007 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) { 6008 return false; 6009 } 6010 } else { 6011 // get command manager and dispatch command to our window if it's acceptable 6012 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager(); 6013 if (!commandManager) { 6014 return false; 6015 } 6016 6017 nsPIDOMWindowOuter* window = GetWindow(); 6018 if (!window) { 6019 return false; 6020 } 6021 6022 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName, 6023 window, params))) { 6024 return false; 6025 } 6026 } 6027 6028 // If command does not have a state_mixed value, this call fails and sets 6029 // retval to false. This is fine -- we want to return false in that case 6030 // anyway (bug 738385), so we just don't throw regardless. 6031 return params->GetBool("state_mixed"); 6032 } 6033 6034 bool Document::QueryCommandState(const nsAString& aHTMLCommandName, 6035 ErrorResult& aRv) { 6036 // Only allow on HTML documents. 6037 if (!IsHTMLOrXHTML()) { 6038 aRv.ThrowInvalidStateError( 6039 "queryCommandState is only supported on HTML documents"); 6040 return false; 6041 } 6042 // Otherwise, don't throw exception for compatibility with Chrome. 6043 6044 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName); 6045 switch (commandData.mCommand) { 6046 case Command::DoNothing: 6047 return false; 6048 case Command::SetDocumentReadOnly: 6049 SetUseCounter( 6050 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly); 6051 break; 6052 case Command::SetDocumentInsertBROnEnterKeyPress: 6053 SetUseCounter( 6054 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn); 6055 break; 6056 default: 6057 break; 6058 } 6059 6060 if (aHTMLCommandName.LowerCaseEqualsLiteral("usecss")) { 6061 // Per spec, state is supported for styleWithCSS but not useCSS, so we just 6062 // return false always. 6063 return false; 6064 } 6065 6066 AutoEditorCommandTarget editCommandTarget(*this, commandData); 6067 if (commandData.IsAvailableOnlyWhenEditable() && 6068 !editCommandTarget.IsEditable(this)) { 6069 return false; 6070 } 6071 RefPtr<nsCommandParams> params = new nsCommandParams(); 6072 if (editCommandTarget.IsEditor()) { 6073 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) { 6074 return false; 6075 } 6076 } else { 6077 // get command manager and dispatch command to our window if it's acceptable 6078 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager(); 6079 if (!commandManager) { 6080 return false; 6081 } 6082 6083 nsPIDOMWindowOuter* window = GetWindow(); 6084 if (!window) { 6085 return false; 6086 } 6087 6088 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName, 6089 window, params))) { 6090 return false; 6091 } 6092 } 6093 6094 // handle alignment as a special case (possibly other commands too?) 6095 // Alignment is special because the external api is individual 6096 // commands but internally we use cmd_align with different 6097 // parameters. When getting the state of this command, we need to 6098 // return the boolean for this particular alignment rather than the 6099 // string of 'which alignment is this?' 6100 switch (commandData.mCommand) { 6101 case Command::FormatJustifyLeft: { 6102 nsAutoCString currentValue; 6103 nsresult rv = params->GetCString("state_attribute", currentValue); 6104 if (NS_FAILED(rv)) { 6105 return false; 6106 } 6107 return currentValue.EqualsLiteral("left"); 6108 } 6109 case Command::FormatJustifyRight: { 6110 nsAutoCString currentValue; 6111 nsresult rv = params->GetCString("state_attribute", currentValue); 6112 if (NS_FAILED(rv)) { 6113 return false; 6114 } 6115 return currentValue.EqualsLiteral("right"); 6116 } 6117 case Command::FormatJustifyCenter: { 6118 nsAutoCString currentValue; 6119 nsresult rv = params->GetCString("state_attribute", currentValue); 6120 if (NS_FAILED(rv)) { 6121 return false; 6122 } 6123 return currentValue.EqualsLiteral("center"); 6124 } 6125 case Command::FormatJustifyFull: { 6126 nsAutoCString currentValue; 6127 nsresult rv = params->GetCString("state_attribute", currentValue); 6128 if (NS_FAILED(rv)) { 6129 return false; 6130 } 6131 return currentValue.EqualsLiteral("justify"); 6132 } 6133 default: 6134 break; 6135 } 6136 6137 // If command does not have a state_all value, this call fails and sets 6138 // retval to false. This is fine -- we want to return false in that case 6139 // anyway (bug 738385), so we just succeed and return false regardless. 6140 return params->GetBool("state_all"); 6141 } 6142 6143 bool Document::QueryCommandSupported(const nsAString& aHTMLCommandName, 6144 nsIPrincipal& aSubjectPrincipal, 6145 ErrorResult& aRv) { 6146 // Only allow on HTML documents. 6147 if (!IsHTMLOrXHTML()) { 6148 aRv.ThrowInvalidStateError( 6149 "queryCommandSupported is only supported on HTML documents"); 6150 return false; 6151 } 6152 // Otherwise, don't throw exception for compatibility with Chrome. 6153 6154 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName); 6155 switch (commandData.mCommand) { 6156 case Command::DoNothing: 6157 return false; 6158 case Command::SetDocumentReadOnly: 6159 SetUseCounter( 6160 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledContentReadOnly); 6161 break; 6162 case Command::SetDocumentInsertBROnEnterKeyPress: 6163 SetUseCounter( 6164 eUseCounter_custom_DocumentQueryCommandSupportedOrEnabledInsertBrOnReturn); 6165 break; 6166 default: 6167 break; 6168 } 6169 6170 // Gecko technically supports all the clipboard commands including 6171 // cut/copy/paste, and depending on the pref "dom.allow_cut_copy", cut and 6172 // copy may also be disallowed to be called from non-privileged content. For 6173 // that reason, we report the support status of corresponding command 6174 // accordingly. 6175 if (commandData.IsPasteCommand() && 6176 !StaticPrefs::dom_execCommand_paste_enabled() && 6177 !nsContentUtils::PrincipalHasPermission(aSubjectPrincipal, 6178 nsGkAtoms::clipboardRead)) { 6179 return false; 6180 } 6181 if (commandData.IsCutOrCopyCommand() && !StaticPrefs::dom_allow_cut_copy() && 6182 !nsContentUtils::PrincipalHasPermission(aSubjectPrincipal, 6183 nsGkAtoms::clipboardWrite)) { 6184 return false; 6185 } 6186 6187 // aHTMLCommandName is supported if it can be converted to a Midas command 6188 return true; 6189 } 6190 6191 void Document::QueryCommandValue(const nsAString& aHTMLCommandName, 6192 nsAString& aValue, ErrorResult& aRv) { 6193 aValue.Truncate(); 6194 6195 // Only allow on HTML documents. 6196 if (!IsHTMLOrXHTML()) { 6197 aRv.ThrowInvalidStateError( 6198 "queryCommandValue is only supported on HTML documents"); 6199 return; 6200 } 6201 // Otherwise, don't throw exception for compatibility with Chrome. 6202 6203 InternalCommandData commandData = ConvertToInternalCommand(aHTMLCommandName); 6204 switch (commandData.mCommand) { 6205 case Command::DoNothing: 6206 // Return empty string 6207 return; 6208 case Command::SetDocumentReadOnly: 6209 SetUseCounter( 6210 eUseCounter_custom_DocumentQueryCommandStateOrValueContentReadOnly); 6211 break; 6212 case Command::SetDocumentInsertBROnEnterKeyPress: 6213 SetUseCounter( 6214 eUseCounter_custom_DocumentQueryCommandStateOrValueInsertBrOnReturn); 6215 break; 6216 default: 6217 break; 6218 } 6219 6220 AutoEditorCommandTarget editCommandTarget(*this, commandData); 6221 if (commandData.IsAvailableOnlyWhenEditable() && 6222 !editCommandTarget.IsEditable(this)) { 6223 return; 6224 } 6225 RefPtr<nsCommandParams> params = new nsCommandParams(); 6226 if (editCommandTarget.IsEditor()) { 6227 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) { 6228 return; 6229 } 6230 6231 if (NS_FAILED(editCommandTarget.GetCommandStateParams(*params))) { 6232 return; 6233 } 6234 } else { 6235 // get command manager and dispatch command to our window if it's acceptable 6236 RefPtr<nsCommandManager> commandManager = GetMidasCommandManager(); 6237 if (!commandManager) { 6238 return; 6239 } 6240 6241 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow(); 6242 if (!window) { 6243 return; 6244 } 6245 6246 if (NS_FAILED(params->SetCString("state_attribute", ""_ns))) { 6247 return; 6248 } 6249 6250 if (NS_FAILED(commandManager->GetCommandState(commandData.mXULCommandName, 6251 window, params))) { 6252 return; 6253 } 6254 } 6255 6256 // If command does not have a state_attribute value, this call fails, and 6257 // aValue will wind up being the empty string. This is fine -- we want to 6258 // return "" in that case anyway (bug 738385), so we just return NS_OK 6259 // regardless. 6260 nsAutoCString result; 6261 params->GetCString("state_attribute", result); 6262 CopyUTF8toUTF16(result, aValue); 6263 } 6264 6265 void Document::MaybeEditingStateChanged() { 6266 if (!mPendingMaybeEditingStateChanged && mMayStartLayout && 6267 mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) { 6268 if (nsContentUtils::IsSafeToRunScript()) { 6269 EditingStateChanged(); 6270 } else if (!mInDestructor) { 6271 nsContentUtils::AddScriptRunner( 6272 NewRunnableMethod("Document::MaybeEditingStateChanged", this, 6273 &Document::MaybeEditingStateChanged)); 6274 } 6275 } 6276 } 6277 6278 void Document::NotifyFetchOrXHRSuccess() { 6279 if (mShouldNotifyFetchSuccess) { 6280 nsContentUtils::DispatchEventOnlyToChrome( 6281 this, this, u"DOMDocFetchSuccess"_ns, CanBubble::eNo, Cancelable::eNo, 6282 /* DefaultAction */ nullptr); 6283 } 6284 } 6285 6286 void Document::SetNotifyFetchSuccess(bool aShouldNotify) { 6287 mShouldNotifyFetchSuccess = aShouldNotify; 6288 } 6289 6290 void Document::SetNotifyFormOrPasswordRemoved(bool aShouldNotify) { 6291 mShouldNotifyFormOrPasswordRemoved = aShouldNotify; 6292 } 6293 6294 void Document::TearingDownEditor() { 6295 if (IsEditingOn()) { 6296 mEditingState = EditingState::eTearingDown; 6297 } 6298 } 6299 6300 nsresult Document::TurnEditingOff() { 6301 NS_ASSERTION(mEditingState != EditingState::eOff, "Editing is already off."); 6302 6303 nsPIDOMWindowOuter* window = GetWindow(); 6304 if (!window) { 6305 return NS_ERROR_FAILURE; 6306 } 6307 6308 nsIDocShell* docshell = GetDocShell(); 6309 if (!docshell || docshell->IsBeingDestroyed()) { 6310 return NS_ERROR_FAILURE; 6311 } 6312 6313 nsCOMPtr<nsIEditingSession> editSession; 6314 MOZ_TRY(docshell->GetEditingSession(getter_AddRefs(editSession))); 6315 6316 // turn editing off 6317 MOZ_TRY(editSession->TearDownEditorOnWindow(window)); 6318 6319 mEditingState = EditingState::eOff; 6320 6321 // Editor resets selection since it is being destroyed. But if focus is 6322 // still into editable control, we have to initialize selection again. 6323 if (RefPtr<TextControlElement> textControlElement = 6324 TextControlElement::FromNodeOrNull( 6325 nsFocusManager::GetFocusedElementStatic())) { 6326 if (RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor()) { 6327 textEditor->ReinitializeSelection(*textControlElement); 6328 } 6329 } 6330 6331 return NS_OK; 6332 } 6333 6334 HTMLEditor* Document::GetHTMLEditor() const { 6335 nsPIDOMWindowOuter* window = GetWindow(); 6336 if (!window) { 6337 return nullptr; 6338 } 6339 6340 nsIDocShell* docshell = window->GetDocShell(); 6341 if (!docshell) { 6342 return nullptr; 6343 } 6344 6345 return docshell->GetHTMLEditor(); 6346 } 6347 6348 nsresult Document::EditingStateChanged() { 6349 if (mRemovedFromDocShell) { 6350 return NS_OK; 6351 } 6352 6353 if (mEditingState == EditingState::eSettingUp || 6354 mEditingState == EditingState::eTearingDown) { 6355 // XXX We shouldn't recurse 6356 return NS_OK; 6357 } 6358 6359 const bool designMode = IsInDesignMode(); 6360 const EditingState newState = 6361 designMode ? EditingState::eDesignMode 6362 : (mContentEditableCount > 0 ? EditingState::eContentEditable 6363 : EditingState::eOff); 6364 if (mEditingState == newState) { 6365 // No changes in editing mode. 6366 return NS_OK; 6367 } 6368 6369 const bool thisDocumentHasFocus = ThisDocumentHasFocus(); 6370 if (newState == EditingState::eOff) { 6371 // Editing is being turned off. 6372 nsAutoScriptBlocker scriptBlocker; 6373 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor(); 6374 nsresult rv = TurnEditingOff(); 6375 // If this document has focus and the editing state of this document 6376 // becomes "off", it means that HTMLEditor won't handle any inputs nor 6377 // modify the DOM tree. However, HTMLEditor may not receive `blur` 6378 // event for this state change since this may occur without focus change. 6379 // Therefore, let's notify HTMLEditor of this editing state change. 6380 // Note that even if focusedElement is an editable text control element, 6381 // it becomes not editable from HTMLEditor point of view since text 6382 // control elements are manged by TextEditor. 6383 RefPtr<Element> focusedElement = nsFocusManager::GetFocusedElementStatic(); 6384 DebugOnly<nsresult> rvIgnored = 6385 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable( 6386 htmlEditor, *this, focusedElement); 6387 NS_WARNING_ASSERTION( 6388 NS_SUCCEEDED(rvIgnored), 6389 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but " 6390 "ignored"); 6391 return rv; 6392 } 6393 6394 const EditingState oldState = mEditingState; 6395 MOZ_ASSERT(newState == EditingState::eDesignMode || 6396 newState == EditingState::eContentEditable); 6397 MOZ_ASSERT_IF(newState == EditingState::eDesignMode, 6398 oldState == EditingState::eContentEditable || 6399 oldState == EditingState::eOff); 6400 MOZ_ASSERT_IF( 6401 newState == EditingState::eContentEditable, 6402 oldState == EditingState::eDesignMode || oldState == EditingState::eOff); 6403 6404 // Flush out style changes on our _parent_ document, if any, so that 6405 // our check for a presshell won't get stale information. 6406 if (mParentDocument) { 6407 mParentDocument->FlushPendingNotifications(FlushType::Style); 6408 } 6409 6410 // get editing session, make sure this is a strong reference so the 6411 // window can't get deleted during the rest of this call. 6412 const nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow(); 6413 if (!window) { 6414 return NS_ERROR_FAILURE; 6415 } 6416 6417 nsIDocShell* docshell = GetDocShell(); 6418 if (!docshell || docshell->IsBeingDestroyed()) { 6419 return NS_ERROR_FAILURE; 6420 } 6421 6422 nsCOMPtr<nsIEditingSession> editSession; 6423 MOZ_TRY(docshell->GetEditingSession(getter_AddRefs(editSession))); 6424 6425 if (RefPtr<HTMLEditor> htmlEditor = docshell->GetHTMLEditor()) { 6426 // We might already have an editor if it was set up for mail, let's see 6427 // if this is actually the case. 6428 uint32_t flags = 0; 6429 htmlEditor->GetFlags(&flags); 6430 if (flags & nsIEditor::eEditorMailMask) { 6431 // We already have a mail editor, then we should not attempt to create 6432 // another one. 6433 return NS_OK; 6434 } 6435 } 6436 6437 RefPtr<PresShell> presShell = GetPresShell(); 6438 if (!presShell) { 6439 // We should not make the window editable or setup its editor. 6440 // It's probably style=display:none. 6441 return NS_OK; 6442 } 6443 6444 bool makeWindowEditable = mEditingState == EditingState::eOff; 6445 bool spellRecheckAll = false; 6446 bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false; 6447 6448 RefPtr<HTMLEditor> htmlEditor; 6449 { 6450 nsAutoEditingState push(this, EditingState::eSettingUp); 6451 6452 // If we're entering the design mode from non-editable state, put the 6453 // selection at the beginning of the document for compatibility reasons. 6454 bool collapseSelectionAtBeginningOfDocument = 6455 designMode && oldState == EditingState::eOff; 6456 // However, mEditingState may be eOff even if there is some 6457 // `contenteditable` area and selection has been initialized for it because 6458 // mEditingState for `contenteditable` may have been scheduled to modify 6459 // when safe. In such case, we should not reinitialize selection. 6460 if (collapseSelectionAtBeginningOfDocument && mContentEditableCount) { 6461 Selection* selection = 6462 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL); 6463 NS_WARNING_ASSERTION(selection, "Why don't we have Selection?"); 6464 if (selection && selection->RangeCount()) { 6465 // Perhaps, we don't need to check whether the selection is in 6466 // an editing host or not because all contents will be editable 6467 // in designMode. (And we don't want to make this code so complicated 6468 // because of legacy API.) 6469 collapseSelectionAtBeginningOfDocument = false; 6470 } 6471 } 6472 6473 MOZ_ASSERT(mStyleSetFilled); 6474 6475 if (designMode) { 6476 // designMode is being turned on (overrides contentEditable). 6477 spellRecheckAll = oldState == EditingState::eContentEditable; 6478 } 6479 6480 // Adjust focused element with new style but blur event shouldn't be fired 6481 // until mEditingState is modified with newState. 6482 nsAutoScriptBlocker scriptBlocker; 6483 if (designMode) { 6484 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; 6485 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant( 6486 window, nsFocusManager::eOnlyCurrentWindow, 6487 getter_AddRefs(focusedWindow)); 6488 if (focusedContent) { 6489 nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame(); 6490 bool clearFocus = focusedFrame 6491 ? !focusedFrame->IsFocusable() 6492 : !focusedContent->IsFocusableWithoutStyle(); 6493 if (clearFocus) { 6494 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 6495 fm->ClearFocus(window); 6496 // If we need to dispatch blur event, we should put off after 6497 // modifying mEditingState since blur event handler may change 6498 // designMode state again. 6499 putOffToRemoveScriptBlockerUntilModifyingEditingState = true; 6500 } 6501 } 6502 } 6503 } 6504 6505 if (makeWindowEditable) { 6506 // Editing is being turned on (through designMode or contentEditable) 6507 // Turn on editor. 6508 // XXX This can cause flushing which can change the editing state, so make 6509 // sure to avoid recursing. 6510 MOZ_TRY( 6511 editSession->MakeWindowEditable(window, "html", false, false, true)); 6512 } 6513 6514 // XXX Need to call TearDownEditorOnWindow for all failures. 6515 htmlEditor = docshell->GetHTMLEditor(); 6516 if (!htmlEditor) { 6517 // Return NS_OK even though we've failed to create an editor here. This 6518 // is so that the setter of designMode on non-HTML documents does not 6519 // fail. 6520 // This is OK to do because in nsEditingSession::SetupEditorOnWindow() we 6521 // would detect that we can't support the mimetype if appropriate and 6522 // would fall onto the eEditorErrorCantEditMimeType path. 6523 return NS_OK; 6524 } 6525 6526 if (collapseSelectionAtBeginningOfDocument) { 6527 htmlEditor->BeginningOfDocument(); 6528 } 6529 6530 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) { 6531 nsContentUtils::AddScriptBlocker(); 6532 } 6533 } 6534 6535 mEditingState = newState; 6536 if (putOffToRemoveScriptBlockerUntilModifyingEditingState) { 6537 nsContentUtils::RemoveScriptBlocker(); 6538 // If mEditingState is overwritten by another call and already disabled 6539 // the editing, we shouldn't keep making window editable. 6540 if (mEditingState == EditingState::eOff) { 6541 return NS_OK; 6542 } 6543 } 6544 6545 if (makeWindowEditable) { 6546 // TODO: We should do this earlier in this method. 6547 // Previously, we called `ExecCommand` with `insertBrOnReturn` command 6548 // whose argument is false here. Then, if it returns error, we 6549 // stopped making it editable. However, after bug 1697078 fixed, 6550 // `ExecCommand` returns error only when the document is not XHTML's 6551 // nor HTML's. Therefore, we use same error handling for now. 6552 if (MOZ_UNLIKELY(NS_WARN_IF(!IsHTMLOrXHTML()))) { 6553 // Editor setup failed. Editing is not on after all. 6554 // XXX Should we reset the editable flag on nodes? 6555 editSession->TearDownEditorOnWindow(window); 6556 mEditingState = EditingState::eOff; 6557 return NS_ERROR_DOM_INVALID_STATE_ERR; 6558 } 6559 // Set the editor to not insert <br> elements on return when in <p> elements 6560 // by default. 6561 htmlEditor->SetReturnInParagraphCreatesNewParagraph(true); 6562 } 6563 6564 // Resync the editor's spellcheck state. 6565 if (spellRecheckAll) { 6566 nsCOMPtr<nsISelectionController> selectionController = 6567 htmlEditor->GetSelectionController(); 6568 if (NS_WARN_IF(!selectionController)) { 6569 return NS_ERROR_FAILURE; 6570 } 6571 6572 RefPtr<Selection> spellCheckSelection = selectionController->GetSelection( 6573 nsISelectionController::SELECTION_SPELLCHECK); 6574 if (spellCheckSelection) { 6575 spellCheckSelection->RemoveAllRanges(IgnoreErrors()); 6576 } 6577 } 6578 htmlEditor->SyncRealTimeSpell(); 6579 6580 MaybeDispatchCheckKeyPressEventModelEvent(); 6581 6582 // If this document keeps having focus, the HTMLEditor may not receive `focus` 6583 // event for this editing state change since this may occur without a focus 6584 // change. Therefore, let's notify HTMLEditor of this editing state change. 6585 if (thisDocumentHasFocus && ThisDocumentHasFocus()) { 6586 RefPtr<Element> focusedElement = nsFocusManager::GetFocusedElementStatic(); 6587 MOZ_ASSERT_IF(focusedElement, focusedElement->GetComposedDoc() == this); 6588 if ((focusedElement && focusedElement->IsEditable() && 6589 (!focusedElement->IsTextControlElement() || 6590 !TextControlElement::FromNode(focusedElement) 6591 ->IsSingleLineTextControlOrTextArea())) || 6592 (!focusedElement && IsInDesignMode())) { 6593 DebugOnly<nsresult> rvIgnored = 6594 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, 6595 focusedElement); 6596 NS_WARNING_ASSERTION( 6597 NS_SUCCEEDED(rvIgnored), 6598 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but " 6599 "ignored"); 6600 } else if (htmlEditor->HasFocus()) { 6601 DebugOnly<nsresult> rvIgnored = 6602 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable( 6603 htmlEditor, *this, focusedElement); 6604 NS_WARNING_ASSERTION( 6605 NS_SUCCEEDED(rvIgnored), 6606 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, " 6607 "but ignored"); 6608 } 6609 } 6610 6611 return NS_OK; 6612 } 6613 6614 // Helper class, used below in ChangeContentEditableCount(). 6615 class DeferredContentEditableCountChangeEvent : public Runnable { 6616 public: 6617 DeferredContentEditableCountChangeEvent(Document* aDoc, Element* aElement) 6618 : mozilla::Runnable("DeferredContentEditableCountChangeEvent"), 6619 mDoc(aDoc), 6620 mElement(aElement) {} 6621 6622 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override { 6623 if (mElement && mElement->OwnerDoc() == mDoc) { 6624 RefPtr<Document> doc = std::move(mDoc); 6625 RefPtr<Element> element = std::move(mElement); 6626 doc->DeferredContentEditableCountChange(element); 6627 } 6628 return NS_OK; 6629 } 6630 6631 private: 6632 RefPtr<Document> mDoc; 6633 RefPtr<Element> mElement; 6634 }; 6635 6636 void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) { 6637 NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0, 6638 "Trying to decrement too much."); 6639 6640 mContentEditableCount += aChange; 6641 6642 if (aElement) { 6643 nsContentUtils::AddScriptRunner( 6644 new DeferredContentEditableCountChangeEvent(this, aElement)); 6645 } 6646 } 6647 6648 void Document::DeferredContentEditableCountChange(Element* aElement) { 6649 const bool elementHasFocus = 6650 aElement && nsFocusManager::GetFocusedElementStatic() == aElement; 6651 if (elementHasFocus) { 6652 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); 6653 // When contenteditable of aElement is changed and HTMLEditor works with it 6654 // or needs to start working with it, HTMLEditor may not receive `focus` 6655 // event nor `blur` event because this may occur without a focus change. 6656 // Therefore, we need to notify HTMLEditor of this contenteditable attribute 6657 // change. 6658 RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor(); 6659 if (aElement->HasFlag(NODE_IS_EDITABLE)) { 6660 if (htmlEditor) { 6661 DebugOnly<nsresult> rvIgnored = 6662 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, 6663 aElement); 6664 NS_WARNING_ASSERTION( 6665 NS_SUCCEEDED(rvIgnored), 6666 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but " 6667 "ignored"); 6668 } 6669 } else { 6670 DebugOnly<nsresult> rvIgnored = 6671 HTMLEditor::FocusedElementOrDocumentBecomesNotEditable( 6672 htmlEditor, *this, aElement); 6673 NS_WARNING_ASSERTION( 6674 NS_SUCCEEDED(rvIgnored), 6675 "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, " 6676 "but ignored"); 6677 } 6678 } 6679 6680 if (mParser || 6681 (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) { 6682 return; 6683 } 6684 6685 EditingState oldState = mEditingState; 6686 6687 nsresult rv = EditingStateChanged(); 6688 NS_ENSURE_SUCCESS_VOID(rv); 6689 6690 if (oldState == mEditingState && 6691 mEditingState == EditingState::eContentEditable) { 6692 // We just changed the contentEditable state of a node, we need to reset 6693 // the spellchecking state of that node. 6694 if (aElement) { 6695 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) { 6696 nsCOMPtr<nsIInlineSpellChecker> spellChecker; 6697 DebugOnly<nsresult> rvIgnored = htmlEditor->GetInlineSpellChecker( 6698 false, getter_AddRefs(spellChecker)); 6699 NS_WARNING_ASSERTION( 6700 NS_SUCCEEDED(rvIgnored), 6701 "EditorBase::GetInlineSpellChecker() failed, but ignored"); 6702 6703 if (spellChecker && 6704 aElement->InclusiveDescendantMayNeedSpellchecking(htmlEditor)) { 6705 RefPtr<nsRange> range = nsRange::Create(aElement); 6706 IgnoredErrorResult res; 6707 range->SelectNodeContents(*aElement, res); 6708 if (res.Failed()) { 6709 // The node might be detached from the document at this point, 6710 // which would cause this call to fail. In this case, we can 6711 // safely ignore the contenteditable count change. 6712 return; 6713 } 6714 6715 rv = spellChecker->SpellCheckRange(range); 6716 NS_ENSURE_SUCCESS_VOID(rv); 6717 } 6718 } 6719 } 6720 } 6721 6722 // aElement causes creating new HTMLEditor and the element had and keep 6723 // having focus, the HTMLEditor won't receive `focus` event. Therefore, we 6724 // need to notify HTMLEditor of it becomes editable. 6725 if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) && 6726 nsFocusManager::GetFocusedElementStatic() == aElement) { 6727 if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) { 6728 DebugOnly<nsresult> rvIgnored = 6729 htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement); 6730 NS_WARNING_ASSERTION( 6731 NS_SUCCEEDED(rvIgnored), 6732 "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but " 6733 "ignored"); 6734 } 6735 } 6736 } 6737 6738 void Document::MaybeDispatchCheckKeyPressEventModelEvent() { 6739 // Currently, we need to check only when we're becoming editable for 6740 // contenteditable. 6741 if (mEditingState != EditingState::eContentEditable) { 6742 return; 6743 } 6744 6745 if (mHasBeenEditable) { 6746 return; 6747 } 6748 mHasBeenEditable = true; 6749 6750 // Dispatch "CheckKeyPressEventModel" event. That is handled only by 6751 // KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel() 6752 // with proper keypress event for the active web app. 6753 WidgetEvent checkEvent(true, eUnidentifiedEvent); 6754 checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel; 6755 checkEvent.mFlags.mCancelable = false; 6756 checkEvent.mFlags.mBubbles = false; 6757 checkEvent.mFlags.mOnlySystemGroupDispatch = true; 6758 // Post the event rather than dispatching it synchronously because we need 6759 // a call of SetKeyPressEventModel() before first key input. Therefore, we 6760 // can avoid paying unnecessary runtime cost for most web apps. 6761 (new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent(); 6762 } 6763 6764 void Document::SetKeyPressEventModel(uint16_t aKeyPressEventModel) { 6765 PresShell* presShell = GetPresShell(); 6766 if (!presShell) { 6767 return; 6768 } 6769 presShell->SetKeyPressEventModel(aKeyPressEventModel); 6770 } 6771 6772 TimeStamp Document::LastFocusTime() const { return mLastFocusTime; } 6773 6774 void Document::SetLastFocusTime(const TimeStamp& aFocusTime) { 6775 MOZ_DIAGNOSTIC_ASSERT(!aFocusTime.IsNull()); 6776 MOZ_DIAGNOSTIC_ASSERT(mLastFocusTime.IsNull() || 6777 aFocusTime >= mLastFocusTime); 6778 mLastFocusTime = aFocusTime; 6779 } 6780 6781 void Document::GetReferrer(nsACString& aReferrer) const { 6782 aReferrer.Truncate(); 6783 if (!mReferrerInfo) { 6784 return; 6785 } 6786 6787 nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer(); 6788 if (!referrer) { 6789 return; 6790 } 6791 6792 URLDecorationStripper::StripTrackingIdentifiers(referrer, aReferrer); 6793 } 6794 6795 void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) { 6796 aCookie.Truncate(); // clear current cookie in case service fails; 6797 // no cookie isn't an error condition. 6798 6799 nsCOMPtr<nsIPrincipal> cookiePrincipal; 6800 nsCOMPtr<nsIPrincipal> cookiePartitionedPrincipal; 6801 6802 CookieCommons::SecurityChecksResult checkResult = 6803 CookieCommons::CheckGlobalAndRetrieveCookiePrincipals( 6804 this, getter_AddRefs(cookiePrincipal), 6805 getter_AddRefs(cookiePartitionedPrincipal)); 6806 switch (checkResult) { 6807 case CookieCommons::SecurityChecksResult::eSandboxedError: 6808 aRv.ThrowSecurityError( 6809 "Forbidden in a sandboxed document without the 'allow-same-origin' " 6810 "flag."); 6811 return; 6812 6813 case CookieCommons::SecurityChecksResult::eSecurityError: 6814 [[fallthrough]]; 6815 6816 case CookieCommons::SecurityChecksResult::eDoNotContinue: 6817 return; 6818 6819 case CookieCommons::SecurityChecksResult::eContinue: 6820 break; 6821 } 6822 6823 bool thirdParty = true; 6824 nsPIDOMWindowInner* innerWindow = GetInnerWindow(); 6825 // in gtests we don't have a window, let's consider those requests as 3rd 6826 // party. 6827 if (innerWindow) { 6828 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); 6829 6830 if (thirdPartyUtil) { 6831 (void)thirdPartyUtil->IsThirdPartyWindow(innerWindow->GetOuterWindow(), 6832 nullptr, &thirdParty); 6833 } 6834 } 6835 6836 nsTArray<nsCOMPtr<nsIPrincipal>> principals; 6837 6838 MOZ_ASSERT(cookiePrincipal); 6839 principals.AppendElement(cookiePrincipal); 6840 6841 if (cookiePartitionedPrincipal) { 6842 principals.AppendElement(cookiePartitionedPrincipal); 6843 } 6844 6845 nsTArray<RefPtr<Cookie>> cookieList; 6846 bool stale = false; 6847 int64_t currentTimeInUsec = PR_Now(); 6848 int64_t currentTimeInMSec = currentTimeInUsec / PR_USEC_PER_MSEC; 6849 6850 // not having a cookie service isn't an error 6851 nsCOMPtr<nsICookieService> service = 6852 do_GetService(NS_COOKIESERVICE_CONTRACTID); 6853 if (!service) { 6854 return; 6855 } 6856 6857 nsCOMPtr<nsILoadInfo> loadInfo = 6858 GetChannel() ? GetChannel()->LoadInfo() : nullptr; 6859 bool on3pcbException = loadInfo && loadInfo->GetIsOn3PCBExceptionList(); 6860 6861 for (auto& principal : principals) { 6862 nsAutoCString baseDomain; 6863 nsresult rv = CookieCommons::GetBaseDomain(principal, baseDomain); 6864 if (NS_WARN_IF(NS_FAILED(rv))) { 6865 return; 6866 } 6867 6868 nsAutoCString hostFromURI; 6869 rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI); 6870 if (NS_WARN_IF(NS_FAILED(rv))) { 6871 return; 6872 } 6873 6874 nsAutoCString pathFromURI; 6875 rv = principal->GetFilePath(pathFromURI); 6876 if (NS_WARN_IF(NS_FAILED(rv))) { 6877 return; 6878 } 6879 6880 nsTArray<RefPtr<Cookie>> cookies; 6881 service->GetCookiesFromHost(baseDomain, principal->OriginAttributesRef(), 6882 cookies); 6883 if (cookies.IsEmpty()) { 6884 continue; 6885 } 6886 6887 // check if the nsIPrincipal is using an https secure protocol. 6888 // if it isn't, then we can't send a secure cookie over the connection. 6889 bool potentiallyTrustworthy = 6890 principal->GetIsOriginPotentiallyTrustworthy(); 6891 6892 // iterate the cookies! 6893 for (Cookie* cookie : cookies) { 6894 // check the host, since the base domain lookup is conservative. 6895 if (!CookieCommons::DomainMatches(cookie, hostFromURI)) { 6896 continue; 6897 } 6898 6899 // if the cookie is httpOnly and it's not going directly to the HTTP 6900 // connection, don't send it 6901 if (cookie->IsHttpOnly()) { 6902 continue; 6903 } 6904 6905 nsCOMPtr<nsIURI> cookieURI = cookiePrincipal->GetURI(); 6906 6907 if (thirdParty && 6908 !CookieCommons::ShouldIncludeCrossSiteCookie( 6909 cookie, cookieURI, CookieJarSettings()->GetPartitionForeign(), 6910 IsInPrivateBrowsing(), UsingStorageAccess(), on3pcbException)) { 6911 continue; 6912 } 6913 6914 // if the cookie is secure and the host scheme isn't, we can't send it 6915 if (cookie->IsSecure() && !potentiallyTrustworthy) { 6916 continue; 6917 } 6918 6919 // if the nsIURI path doesn't match the cookie path, don't send it back 6920 if (!CookieCommons::PathMatches(cookie, pathFromURI)) { 6921 continue; 6922 } 6923 6924 // check if the cookie has expired 6925 if (cookie->ExpiryInMSec() <= currentTimeInMSec) { 6926 continue; 6927 } 6928 6929 // all checks passed - add to list and check if lastAccessed stamp needs 6930 // updating 6931 cookieList.AppendElement(cookie); 6932 if (cookie->IsStale()) { 6933 stale = true; 6934 } 6935 } 6936 } 6937 6938 if (cookieList.IsEmpty()) { 6939 return; 6940 } 6941 6942 // update lastAccessed timestamps. we only do this if the timestamp is stale 6943 // by a certain amount, to avoid thrashing the db during pageload. 6944 if (stale) { 6945 service->StaleCookies(cookieList, currentTimeInUsec); 6946 } 6947 6948 // return cookies in order of path length; longest to shortest. 6949 // this is required per RFC2109. if cookies match in length, 6950 // then sort by creation time (see bug 236772). 6951 cookieList.Sort(CompareCookiesForSending()); 6952 6953 nsAutoCString cookieString; 6954 CookieCommons::ComposeCookieString(cookieList, cookieString); 6955 6956 // CopyUTF8toUTF16 doesn't handle error 6957 // because it assumes that the input is valid. 6958 UTF_8_ENCODING->DecodeWithoutBOMHandling(cookieString, aCookie); 6959 } 6960 6961 void Document::SetCookie(const nsAString& aCookieString, ErrorResult& aRv) { 6962 nsCOMPtr<nsIPrincipal> cookiePrincipal; 6963 6964 CookieCommons::SecurityChecksResult checkResult = 6965 CookieCommons::CheckGlobalAndRetrieveCookiePrincipals( 6966 this, getter_AddRefs(cookiePrincipal), nullptr); 6967 switch (checkResult) { 6968 case CookieCommons::SecurityChecksResult::eSandboxedError: 6969 aRv.ThrowSecurityError( 6970 "Forbidden in a sandboxed document without the 'allow-same-origin' " 6971 "flag."); 6972 return; 6973 6974 case CookieCommons::SecurityChecksResult::eSecurityError: 6975 [[fallthrough]]; 6976 6977 case CookieCommons::SecurityChecksResult::eDoNotContinue: 6978 return; 6979 6980 case CookieCommons::SecurityChecksResult::eContinue: 6981 break; 6982 } 6983 6984 if (!mDocumentURI) { 6985 return; 6986 } 6987 6988 // not having a cookie service isn't an error 6989 nsCOMPtr<nsICookieService> service = 6990 do_GetService(NS_COOKIESERVICE_CONTRACTID); 6991 if (!service) { 6992 return; 6993 } 6994 6995 NS_ConvertUTF16toUTF8 cookieString(aCookieString); 6996 6997 nsCOMPtr<nsIURI> documentURI; 6998 nsAutoCString baseDomain; 6999 OriginAttributes attrs; 7000 7001 int64_t currentTimeInUsec = PR_Now(); 7002 7003 auto* basePrincipal = BasePrincipal::Cast(NodePrincipal()); 7004 basePrincipal->GetURI(getter_AddRefs(documentURI)); 7005 if (NS_WARN_IF(!documentURI)) { 7006 // Document's principal is not a content or null (may be system), so 7007 // can't set cookies 7008 return; 7009 } 7010 7011 // Console report takes care of the correct reporting at the exit of this 7012 // method. 7013 RefPtr<ConsoleReportCollector> crc = new ConsoleReportCollector(); 7014 auto scopeExit = MakeScopeExit([&] { crc->FlushConsoleReports(this); }); 7015 7016 CookieParser cookieParser(crc, documentURI); 7017 7018 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); 7019 if (!thirdPartyUtil) { 7020 return; 7021 } 7022 7023 nsCOMPtr<nsIEffectiveTLDService> tldService = 7024 mozilla::components::EffectiveTLD::Service(); 7025 if (!tldService) { 7026 return; 7027 } 7028 7029 RefPtr<Cookie> cookie = CookieCommons::CreateCookieFromDocument( 7030 cookieParser, this, cookieString, currentTimeInUsec, tldService, 7031 thirdPartyUtil, baseDomain, attrs); 7032 if (!cookie) { 7033 return; 7034 } 7035 7036 bool thirdParty = true; 7037 nsPIDOMWindowInner* innerWindow = GetInnerWindow(); 7038 // in gtests we don't have a window, let's consider those requests as 3rd 7039 // party. 7040 if (innerWindow) { 7041 (void)thirdPartyUtil->IsThirdPartyWindow(innerWindow->GetOuterWindow(), 7042 nullptr, &thirdParty); 7043 } 7044 7045 nsCOMPtr<nsILoadInfo> loadInfo = 7046 GetChannel() ? GetChannel()->LoadInfo() : nullptr; 7047 bool on3pcbException = loadInfo && loadInfo->GetIsOn3PCBExceptionList(); 7048 7049 if (thirdParty && 7050 !CookieCommons::ShouldIncludeCrossSiteCookie( 7051 cookie, documentURI, CookieJarSettings()->GetPartitionForeign(), 7052 IsInPrivateBrowsing(), UsingStorageAccess(), on3pcbException)) { 7053 return; 7054 } 7055 7056 // add the cookie to the list. AddCookieFromDocument() takes care of logging. 7057 service->AddCookieFromDocument(cookieParser, baseDomain, attrs, *cookie, 7058 currentTimeInUsec, documentURI, thirdParty, 7059 this); 7060 7061 nsCOMPtr<nsIObserverService> observerService = 7062 mozilla::services::GetObserverService(); 7063 if (observerService) { 7064 observerService->NotifyObservers(ToSupports(this), "document-set-cookie", 7065 nsString(aCookieString).get()); 7066 } 7067 } 7068 7069 ReferrerPolicy Document::GetReferrerPolicy() const { 7070 return mReferrerInfo ? mReferrerInfo->ReferrerPolicy() 7071 : ReferrerPolicy::_empty; 7072 } 7073 7074 void Document::GetAlinkColor(nsAString& aAlinkColor) { 7075 aAlinkColor.Truncate(); 7076 7077 HTMLBodyElement* body = GetBodyElement(); 7078 if (body) { 7079 body->GetALink(aAlinkColor); 7080 } 7081 } 7082 7083 void Document::SetAlinkColor(const nsAString& aAlinkColor) { 7084 HTMLBodyElement* body = GetBodyElement(); 7085 if (body) { 7086 body->SetALink(aAlinkColor); 7087 } 7088 } 7089 7090 void Document::GetLinkColor(nsAString& aLinkColor) { 7091 aLinkColor.Truncate(); 7092 7093 HTMLBodyElement* body = GetBodyElement(); 7094 if (body) { 7095 body->GetLink(aLinkColor); 7096 } 7097 } 7098 7099 void Document::SetLinkColor(const nsAString& aLinkColor) { 7100 HTMLBodyElement* body = GetBodyElement(); 7101 if (body) { 7102 body->SetLink(aLinkColor); 7103 } 7104 } 7105 7106 void Document::GetVlinkColor(nsAString& aVlinkColor) { 7107 aVlinkColor.Truncate(); 7108 7109 HTMLBodyElement* body = GetBodyElement(); 7110 if (body) { 7111 body->GetVLink(aVlinkColor); 7112 } 7113 } 7114 7115 void Document::SetVlinkColor(const nsAString& aVlinkColor) { 7116 HTMLBodyElement* body = GetBodyElement(); 7117 if (body) { 7118 body->SetVLink(aVlinkColor); 7119 } 7120 } 7121 7122 void Document::GetBgColor(nsAString& aBgColor) { 7123 aBgColor.Truncate(); 7124 7125 HTMLBodyElement* body = GetBodyElement(); 7126 if (body) { 7127 body->GetBgColor(aBgColor); 7128 } 7129 } 7130 7131 void Document::SetBgColor(const nsAString& aBgColor) { 7132 HTMLBodyElement* body = GetBodyElement(); 7133 if (body) { 7134 body->SetBgColor(aBgColor); 7135 } 7136 } 7137 7138 void Document::GetFgColor(nsAString& aFgColor) { 7139 aFgColor.Truncate(); 7140 7141 HTMLBodyElement* body = GetBodyElement(); 7142 if (body) { 7143 body->GetText(aFgColor); 7144 } 7145 } 7146 7147 void Document::SetFgColor(const nsAString& aFgColor) { 7148 HTMLBodyElement* body = GetBodyElement(); 7149 if (body) { 7150 body->SetText(aFgColor); 7151 } 7152 } 7153 7154 void Document::CaptureEvents() { 7155 WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents); 7156 } 7157 7158 void Document::ReleaseEvents() { 7159 WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents); 7160 } 7161 7162 HTMLAllCollection* Document::All() { 7163 if (!mAll) { 7164 mAll = new HTMLAllCollection(this); 7165 } 7166 return mAll; 7167 } 7168 7169 nsresult Document::GetSrcdocData(nsAString& aSrcdocData) { 7170 if (mIsSrcdocDocument) { 7171 nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel); 7172 if (inStrmChan) { 7173 return inStrmChan->GetSrcdocData(aSrcdocData); 7174 } 7175 } 7176 aSrcdocData = VoidString(); 7177 return NS_OK; 7178 } 7179 7180 Nullable<WindowProxyHolder> Document::GetDefaultView() const { 7181 nsPIDOMWindowOuter* win = GetWindow(); 7182 if (!win) { 7183 return nullptr; 7184 } 7185 return WindowProxyHolder(win->GetBrowsingContext()); 7186 } 7187 7188 nsIContent* Document::GetUnretargetedFocusedContent( 7189 IncludeChromeOnly aIncludeChromeOnly) const { 7190 nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow(); 7191 if (!window) { 7192 return nullptr; 7193 } 7194 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; 7195 nsIContent* focusedContent = nsFocusManager::GetFocusedDescendant( 7196 window, nsFocusManager::eOnlyCurrentWindow, 7197 getter_AddRefs(focusedWindow)); 7198 if (!focusedContent) { 7199 return nullptr; 7200 } 7201 // be safe and make sure the element is from this document 7202 if (focusedContent->OwnerDoc() != this) { 7203 return nullptr; 7204 } 7205 if (focusedContent->ChromeOnlyAccess() && 7206 aIncludeChromeOnly == IncludeChromeOnly::No) { 7207 return focusedContent->FindFirstNonChromeOnlyAccessContent(); 7208 } 7209 return focusedContent; 7210 } 7211 7212 Element* Document::GetActiveElement() { 7213 // Get the focused element. 7214 Element* focusedElement = GetRetargetedFocusedElement(); 7215 if (focusedElement) { 7216 return focusedElement; 7217 } 7218 7219 // No focused element anywhere in this document. Try to get the BODY. 7220 if (IsHTMLOrXHTML()) { 7221 Element* bodyElement = AsHTMLDocument()->GetBody(); 7222 if (bodyElement) { 7223 return bodyElement; 7224 } 7225 // Special case to handle the transition to XHTML from XUL documents 7226 // where there currently isn't a body element, but we need to match the 7227 // XUL behavior. This should be removed when bug 1540278 is resolved. 7228 if (nsContentUtils::IsChromeDoc(this)) { 7229 Element* docElement = GetDocumentElement(); 7230 if (docElement && docElement->IsXULElement()) { 7231 return docElement; 7232 } 7233 } 7234 // Because of IE compatibility, return null when html document doesn't have 7235 // a body. 7236 return nullptr; 7237 } 7238 7239 // If we couldn't get a BODY, return the root element. 7240 return GetDocumentElement(); 7241 } 7242 7243 Element* Document::GetCurrentScript() { 7244 if (!mScriptLoader) { 7245 return nullptr; 7246 } 7247 nsCOMPtr<Element> el(do_QueryInterface(mScriptLoader->GetCurrentScript())); 7248 return el; 7249 } 7250 7251 void Document::ReleaseCapture() const { 7252 // only release the capture if the caller can access it. This prevents a 7253 // page from stopping a scrollbar grab for example. 7254 nsCOMPtr<nsINode> node = PresShell::GetCapturingContent(); 7255 if (node && nsContentUtils::CanCallerAccess(node)) { 7256 PresShell::ReleaseCapturingContent(); 7257 } 7258 } 7259 7260 nsIURI* Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const { 7261 if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) { 7262 return mChromeXHRDocBaseURI; 7263 } 7264 7265 return GetDocBaseURI(); 7266 } 7267 7268 void Document::SetBaseURI(nsIURI* aURI) { 7269 if (!aURI && !mDocumentBaseURI) { 7270 return; 7271 } 7272 7273 // Don't do anything if the URI wasn't actually changed. 7274 if (aURI && mDocumentBaseURI) { 7275 bool equalBases = false; 7276 mDocumentBaseURI->Equals(aURI, &equalBases); 7277 if (equalBases) { 7278 return; 7279 } 7280 } 7281 7282 mDocumentBaseURI = aURI; 7283 mCachedURLData = nullptr; 7284 RefreshLinkHrefs(); 7285 } 7286 7287 Result<OwningNonNull<nsIURI>, nsresult> Document::ResolveWithBaseURI( 7288 const nsAString& aURI) { 7289 RefPtr<nsIURI> resolvedURI; 7290 MOZ_TRY( 7291 NS_NewURI(getter_AddRefs(resolvedURI), aURI, nullptr, GetDocBaseURI())); 7292 return OwningNonNull<nsIURI>(std::move(resolvedURI)); 7293 } 7294 7295 nsIReferrerInfo* Document::ReferrerInfoForInternalCSSAndSVGResources() { 7296 if (!mCachedReferrerInfoForInternalCSSAndSVGResources) { 7297 mCachedReferrerInfoForInternalCSSAndSVGResources = 7298 ReferrerInfo::CreateForInternalCSSAndSVGResources(this); 7299 } 7300 return mCachedReferrerInfoForInternalCSSAndSVGResources; 7301 } 7302 7303 URLExtraData* Document::DefaultStyleAttrURLData() { 7304 MOZ_ASSERT(NS_IsMainThread()); 7305 if (!mCachedURLData) { 7306 mCachedURLData = new URLExtraData( 7307 GetDocBaseURI(), ReferrerInfoForInternalCSSAndSVGResources(), 7308 NodePrincipal()); 7309 } 7310 return mCachedURLData; 7311 } 7312 7313 void Document::SetDocumentCharacterSet(NotNull<const Encoding*> aEncoding) { 7314 if (mCharacterSet != aEncoding) { 7315 mCharacterSet = aEncoding; 7316 mEncodingMenuDisabled = aEncoding == UTF_8_ENCODING; 7317 RecomputeLanguageFromCharset(); 7318 7319 if (nsPresContext* context = GetPresContext()) { 7320 context->DocumentCharSetChanged(aEncoding); 7321 } 7322 } 7323 } 7324 7325 void Document::GetSandboxFlagsAsString(nsAString& aFlags) { 7326 nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags); 7327 } 7328 7329 void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const { 7330 aData.Truncate(); 7331 const HeaderData* data = mHeaderData.get(); 7332 while (data) { 7333 if (data->mField == aHeaderField) { 7334 aData = data->mData; 7335 break; 7336 } 7337 data = data->mNext.get(); 7338 } 7339 } 7340 7341 static bool IsValidOnionLocation(nsIURI* aDocumentURI, 7342 nsIURI* aOnionLocationURI) { 7343 if (!aDocumentURI || !aOnionLocationURI) { 7344 return false; 7345 } 7346 7347 // Current URI 7348 nsAutoCString host; 7349 if (!aDocumentURI->SchemeIs("https")) { 7350 return false; 7351 } 7352 NS_ENSURE_SUCCESS(aDocumentURI->GetAsciiHost(host), false); 7353 if (StringEndsWith(host, ".onion"_ns)) { 7354 // Already in the .onion site 7355 return false; 7356 } 7357 7358 // Target URI 7359 if (!aOnionLocationURI->SchemeIs("http") && 7360 !aOnionLocationURI->SchemeIs("https")) { 7361 return false; 7362 } 7363 nsCOMPtr<nsIEffectiveTLDService> eTLDService = 7364 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); 7365 if (!eTLDService) { 7366 NS_ENSURE_SUCCESS(aOnionLocationURI->GetAsciiHost(host), false); 7367 // This should not happen, but in the unlikely case, still check if it is a 7368 // .onion and in case allow it. 7369 return StringEndsWith(host, ".onion"_ns); 7370 } 7371 NS_ENSURE_SUCCESS(eTLDService->GetBaseDomain(aOnionLocationURI, 0, host), 7372 false); 7373 if (!StringEndsWith(host, ".onion"_ns)) { 7374 return false; 7375 } 7376 7377 // Ignore v2 7378 if (host.Length() == 22) { 7379 const char* cur = host.BeginWriting(); 7380 // We have already checked that it ends by ".onion" 7381 const char* end = host.EndWriting() - 6; 7382 bool base32 = true; 7383 for (; cur < end && base32; ++cur) { 7384 base32 = isalpha(*cur) || ('2' <= *cur && *cur <= '7'); 7385 } 7386 return !base32; 7387 } 7388 7389 return true; 7390 } 7391 7392 void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) { 7393 if (!aHeaderField) { 7394 NS_ERROR("null headerField"); 7395 return; 7396 } 7397 7398 if (!mHeaderData) { 7399 if (!aData.IsEmpty()) { // don't bother storing empty string 7400 mHeaderData = MakeUnique<HeaderData>(aHeaderField, aData); 7401 } 7402 } else { 7403 HeaderData* data = mHeaderData.get(); 7404 UniquePtr<HeaderData>* lastPtr = &mHeaderData; 7405 bool found = false; 7406 do { // look for existing and replace 7407 if (data->mField == aHeaderField) { 7408 if (!aData.IsEmpty()) { 7409 data->mData.Assign(aData); 7410 } else { // don't store empty string 7411 // Note that data->mNext is moved to a temporary before the old value 7412 // of *lastPtr is deleted. 7413 *lastPtr = std::move(data->mNext); 7414 } 7415 found = true; 7416 7417 break; 7418 } 7419 lastPtr = &data->mNext; 7420 data = lastPtr->get(); 7421 } while (data); 7422 7423 if (!aData.IsEmpty() && !found) { 7424 // didn't find, append 7425 *lastPtr = MakeUnique<HeaderData>(aHeaderField, aData); 7426 } 7427 } 7428 7429 if (aHeaderField == nsGkAtoms::headerContentLanguage) { 7430 if (aData.IsEmpty()) { 7431 mContentLanguage = nullptr; 7432 } else { 7433 mContentLanguage = NS_AtomizeMainThread(aData); 7434 } 7435 mMayNeedFontPrefsUpdate = true; 7436 if (auto* presContext = GetPresContext()) { 7437 presContext->ContentLanguageChanged(); 7438 } 7439 } 7440 7441 if (aHeaderField == nsGkAtoms::origin_trial) { 7442 mTrials.UpdateFromToken(aData, NodePrincipal()); 7443 if (mTrials.IsEnabled(OriginTrial::CoepCredentialless)) { 7444 InitCOEP(mChannel); 7445 7446 // If we still don't have a WindowContext, WindowContext::OnNewDocument 7447 // will take care of this. 7448 if (WindowContext* ctx = GetWindowContext()) { 7449 if (mEmbedderPolicy) { 7450 (void)ctx->SetEmbedderPolicy(mEmbedderPolicy.value()); 7451 } 7452 } 7453 } 7454 } 7455 7456 if (aHeaderField == nsGkAtoms::headerDefaultStyle) { 7457 SetPreferredStyleSheetSet(aData); 7458 } 7459 7460 if (aHeaderField == nsGkAtoms::refresh && !IsStaticDocument()) { 7461 // We get into this code before we have a script global yet, so get to our 7462 // container via mDocumentContainer. 7463 if (mDocumentContainer) { 7464 // Note: using mDocumentURI instead of mBaseURI here, for consistency 7465 // (used to just use the current URI of our webnavigation, but that 7466 // should really be the same thing). Note that this code can run 7467 // before the current URI of the webnavigation has been updated, so we 7468 // can't assert equality here. 7469 mDocumentContainer->SetupRefreshURIFromHeader(this, aData); 7470 } 7471 } 7472 7473 if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl && 7474 mAllowDNSPrefetch) { 7475 // Chromium treats any value other than 'on' (case insensitive) as 'off'. 7476 mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on"); 7477 } 7478 7479 if (aHeaderField == nsGkAtoms::handheldFriendly) { 7480 mViewportType = Unknown; 7481 } 7482 7483 if (aHeaderField == nsGkAtoms::headerOnionLocation && !aData.IsEmpty()) { 7484 nsCOMPtr<nsIURI> onionURI; 7485 if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(onionURI), aData)) && 7486 IsValidOnionLocation(Document::GetDocumentURI(), onionURI)) { 7487 mOnionLocationURI = onionURI; 7488 } 7489 } 7490 } 7491 7492 void Document::SetEarlyHints( 7493 nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) { 7494 mEarlyHints = std::move(aEarlyHints); 7495 } 7496 7497 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource, 7498 NotNull<const Encoding*>& aEncoding, 7499 nsHtml5TreeOpExecutor* aExecutor) { 7500 if (aChannel) { 7501 nsAutoCString charsetVal; 7502 nsresult rv = aChannel->GetContentCharset(charsetVal); 7503 if (NS_SUCCEEDED(rv)) { 7504 const Encoding* preferred = Encoding::ForLabel(charsetVal); 7505 if (preferred) { 7506 if (aExecutor && preferred == REPLACEMENT_ENCODING) { 7507 aExecutor->ComplainAboutBogusProtocolCharset(this, false); 7508 } 7509 aEncoding = WrapNotNull(preferred); 7510 aCharsetSource = kCharsetFromChannel; 7511 return; 7512 } else if (aExecutor && !charsetVal.IsEmpty()) { 7513 aExecutor->ComplainAboutBogusProtocolCharset(this, true); 7514 } 7515 } 7516 } 7517 } 7518 7519 static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) { 7520 #ifdef DEBUG 7521 for (nsINode* node : ShadowIncludingTreeIterator(aSubtreeRoot)) { 7522 const Element* element = Element::FromNode(node); 7523 if (!element) { 7524 continue; 7525 } 7526 MOZ_ASSERT(!element->HasServoData()); 7527 } 7528 #endif 7529 } 7530 7531 already_AddRefed<PresShell> Document::CreatePresShell( 7532 nsPresContext* aContext, nsSubDocumentFrame* aEmbedderFrame) { 7533 MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!"); 7534 7535 NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr); 7536 7537 AssertNoStaleServoDataIn(*this); 7538 7539 RefPtr<PresShell> presShell = new PresShell(this); 7540 // Note: we don't hold a ref to the shell (it holds a ref to us) 7541 mPresShell = presShell; 7542 7543 if (aEmbedderFrame) { 7544 // It's important to do this as soon as possible so that 7545 // GetRootPresContext() and so on do the right thing from the get go. 7546 aEmbedderFrame->AddEmbeddingPresShell(presShell); 7547 } 7548 7549 if (!mStyleSetFilled) { 7550 FillStyleSet(); 7551 } 7552 7553 presShell->Init(aContext); 7554 if (RefPtr<class HighlightRegistry> highlightRegistry = mHighlightRegistry) { 7555 highlightRegistry->AddHighlightSelectionsToFrameSelection(); 7556 } 7557 // Gaining a shell causes changes in how media queries are evaluated, so 7558 // invalidate that. 7559 aContext->MediaFeatureValuesChanged( 7560 {MediaFeatureChange::kAllChanges}, 7561 MediaFeatureChangePropagation::JustThisDocument); 7562 7563 // Make sure to never paint if we belong to an invisible DocShell. 7564 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer); 7565 if (docShell && docShell->IsInvisible()) { 7566 presShell->SetNeverPainting(true); 7567 } 7568 7569 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, 7570 ("DOCUMENT %p with PressShell %p and DocShell %p", this, 7571 presShell.get(), docShell.get())); 7572 7573 mExternalResourceMap.ShowViewers(); 7574 7575 if (mDocumentL10n) { 7576 // In case we already accumulated mutations, 7577 // we'll trigger the refresh driver now. 7578 mDocumentL10n->OnCreatePresShell(); 7579 } 7580 7581 // Now that we have a shell, we might have @font-face rules (the presence of a 7582 // shell may change which rules apply to us). We don't need to do anything 7583 // like EnsureStyleFlush or such, there's nothing to update yet and when stuff 7584 // is ready to update we'll flush the font set. 7585 MarkUserFontSetDirty(); 7586 7587 // Take the author style disabled state from the top browsing cvontext. 7588 // (PageStyleChild.sys.mjs ensures this is up to date.) 7589 if (BrowsingContext* bc = GetBrowsingContext()) { 7590 presShell->SetAuthorStyleDisabled(bc->Top()->AuthorStyleDisabledDefault()); 7591 } 7592 7593 // We may need to set up the editor now if we haven't earlier, since we avoid 7594 // setting up the editor without a pres shell. 7595 MaybeEditingStateChanged(); 7596 return presShell.forget(); 7597 } 7598 7599 // This roughly matches https://html.spec.whatwg.org/#update-the-rendering 7600 // step 3: 7601 // 7602 // Remove from docs any Document object doc for which any of the 7603 // following are true. 7604 // 7605 // If this function changes make sure to call MaybeScheduleRendering at the 7606 // right places. 7607 bool Document::IsRenderingSuppressed() const { 7608 // TODO(emilio): Per spec we should suppress when doc's visibility state is 7609 // "hidden", but tests rely on throttling at least (otherwise webdriver tests 7610 // that test minimized windows and so on time out). Maybe we can do it only in 7611 // content or something along those lines? 7612 // if (Hidden()) { 7613 // return true; 7614 // } 7615 7616 // doc's rendering is suppressed for view transitions 7617 if (mRenderingSuppressedForViewTransitions) { 7618 return true; 7619 } 7620 // The user agent believes that updating the rendering of doc's node navigable 7621 // would have no visible effect. 7622 if (!IsEventHandlingEnabled() && !IsBeingUsedAsImage() && !mDisplayDocument && 7623 !mPausedByDevTools) { 7624 return true; 7625 } 7626 if (!mPresShell || !mPresShell->DidInitialize()) { 7627 return true; 7628 } 7629 return false; 7630 } 7631 7632 void Document::MaybeScheduleRenderingPhases(RenderingPhases aPhases) { 7633 if (IsRenderingSuppressed()) { 7634 return; 7635 } 7636 MOZ_ASSERT(mPresShell); 7637 nsRefreshDriver* rd = mPresShell->GetPresContext()->RefreshDriver(); 7638 rd->ScheduleRenderingPhases(aPhases); 7639 } 7640 7641 void Document::TakeVideoFrameRequestCallbacks( 7642 nsTArray<RefPtr<HTMLVideoElement>>& aVideoCallbacks) { 7643 MOZ_ASSERT(aVideoCallbacks.IsEmpty()); 7644 mFrameRequestManager.Take(aVideoCallbacks); 7645 } 7646 7647 bool Document::ShouldThrottleFrameRequests() const { 7648 if (mStaticCloneCount > 0) { 7649 // Even if we're not visible, a static clone may be, so run at full speed. 7650 return false; 7651 } 7652 7653 if (Hidden() && !StaticPrefs::layout_testing_top_level_always_active()) { 7654 // We're not visible (probably in a background tab or the bf cache). 7655 return true; 7656 } 7657 7658 if (!mPresShell) { 7659 // Can't do anything smarter. We don't run frame requests in documents 7660 // without a pres shell anyways. 7661 return false; 7662 } 7663 7664 if (!mPresShell->IsActive()) { 7665 // The pres shell is not active (we're an invisible OOP iframe or such), so 7666 // throttle. 7667 return true; 7668 } 7669 7670 if (mPresShell->IsPaintingSuppressed()) { 7671 // Historically we have throttled frame requests until we've painted at 7672 // least once, so keep doing that. 7673 return true; 7674 } 7675 7676 if (mPresShell->IsUnderHiddenEmbedderElement()) { 7677 // For display: none and visibility: hidden we always throttle, for 7678 // consistency with OOP iframes. 7679 return true; 7680 } 7681 7682 Element* el = GetEmbedderElement(); 7683 if (!el) { 7684 // If we're not in-process, our refresh driver is throttled separately (via 7685 // PresShell::SetIsActive, so not much more we can do here. 7686 return false; 7687 } 7688 7689 if (!StaticPrefs::layout_throttle_in_process_iframes()) { 7690 return false; 7691 } 7692 7693 // Note that because we have to scroll this document into view at least once 7694 // to un-throttle it, we will drop one requestAnimationFrame frame when a 7695 // document that previously wasn't visible scrolls into view. This is 7696 // acceptable / unlikely to be human-perceivable, though we could improve on 7697 // it if needed by adding an intersection margin or something of that sort. 7698 const IntersectionInput input = 7699 DOMIntersectionObserver::ComputeInputForIframeThrottling(*el->OwnerDoc()); 7700 const IntersectionOutput output = DOMIntersectionObserver::Intersect( 7701 input, *el, DOMIntersectionObserver::BoxToUse::Content); 7702 return !output.Intersects(); 7703 } 7704 7705 void Document::DeletePresShell() { 7706 mExternalResourceMap.HideViewers(); 7707 mPendingFullscreenEvents.Clear(); 7708 7709 // When our shell goes away, request that all our images be immediately 7710 // discarded, so we don't carry around decoded image data for a document we 7711 // no longer intend to paint. 7712 for (imgIRequest* image : mTrackedImages.Keys()) { 7713 image->RequestDiscard(); 7714 } 7715 7716 // Now that we no longer have a shell, we need to forget about any FontFace 7717 // objects for @font-face rules that came from the style set. There's no need 7718 // to call EnsureStyleFlush either, the shell is going away anyway, so there's 7719 // no point on it. 7720 mFontFaceSetDirty = true; 7721 7722 if (IsEditingOn()) { 7723 TurnEditingOff(); 7724 } 7725 7726 mPresShell = nullptr; 7727 7728 ClearStaleServoData(); 7729 AssertNoStaleServoDataIn(*this); 7730 7731 mStyleSet->ShellDetachedFromDocument(); 7732 mStyleSetFilled = false; 7733 mQuirkSheetAdded = false; 7734 } 7735 7736 void Document::DisallowBFCaching(uint32_t aStatus) { 7737 NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!"); 7738 if (!mBFCacheDisallowed) { 7739 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) { 7740 wgc->SendUpdateBFCacheStatus(aStatus, 0); 7741 } 7742 } 7743 mBFCacheDisallowed = true; 7744 } 7745 7746 void Document::SetBFCacheEntry(nsIBFCacheEntry* aEntry) { 7747 MOZ_ASSERT(IsBFCachingAllowed() || !aEntry, "You should have checked!"); 7748 7749 if (mPresShell) { 7750 if (!aEntry && mBFCacheEntry) { 7751 mPresShell->StartObservingRefreshDriver(); 7752 } 7753 } 7754 mBFCacheEntry = aEntry; 7755 } 7756 7757 bool Document::RemoveFromBFCacheSync() { 7758 bool removed = false; 7759 if (nsCOMPtr<nsIBFCacheEntry> entry = GetBFCacheEntry()) { 7760 entry->RemoveFromBFCacheSync(); 7761 removed = true; 7762 } else if (!IsCurrentActiveDocument()) { 7763 // In the old bfcache implementation while the new page is loading, but 7764 // before nsIDocumentViewer.show() has been called, the previous page 7765 // doesn't yet have nsIBFCacheEntry. However, the previous page isn't the 7766 // current active document anymore. 7767 DisallowBFCaching(); 7768 removed = true; 7769 } 7770 7771 if (mozilla::SessionHistoryInParent() && XRE_IsContentProcess()) { 7772 if (BrowsingContext* bc = GetBrowsingContext()) { 7773 if (bc->IsInBFCache()) { 7774 ContentChild* cc = ContentChild::GetSingleton(); 7775 // IPC is asynchronous but the caller is supposed to check the return 7776 // value. The reason for 'Sync' in the method name is that the old 7777 // implementation may run scripts. There is Async variant in 7778 // the old session history implementation for the cases where 7779 // synchronous operation isn't safe. 7780 cc->SendRemoveFromBFCache(bc->Top()); 7781 removed = true; 7782 } 7783 } 7784 } 7785 return removed; 7786 } 7787 7788 static void SubDocClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry) { 7789 SubDocMapEntry* e = static_cast<SubDocMapEntry*>(entry); 7790 7791 NS_RELEASE(e->mKey); 7792 if (e->mSubDocument) { 7793 e->mSubDocument->SetParentDocument(nullptr); 7794 NS_RELEASE(e->mSubDocument); 7795 } 7796 } 7797 7798 static void SubDocInitEntry(PLDHashEntryHdr* entry, const void* key) { 7799 SubDocMapEntry* e = 7800 const_cast<SubDocMapEntry*>(static_cast<const SubDocMapEntry*>(entry)); 7801 7802 e->mKey = const_cast<Element*>(static_cast<const Element*>(key)); 7803 NS_ADDREF(e->mKey); 7804 7805 e->mSubDocument = nullptr; 7806 } 7807 7808 nsresult Document::SetSubDocumentFor(Element* aElement, Document* aSubDoc) { 7809 NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED); 7810 7811 if (!aSubDoc) { 7812 // aSubDoc is nullptr, remove the mapping 7813 7814 if (mSubDocuments) { 7815 mSubDocuments->Remove(aElement); 7816 } 7817 } else { 7818 if (!mSubDocuments) { 7819 // Create a new hashtable 7820 7821 static const PLDHashTableOps hash_table_ops = { 7822 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub, 7823 PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry}; 7824 7825 mSubDocuments = 7826 MakeUnique<PLDHashTable>(&hash_table_ops, sizeof(SubDocMapEntry)); 7827 } 7828 7829 // Add a mapping to the hash table 7830 auto entry = 7831 static_cast<SubDocMapEntry*>(mSubDocuments->Add(aElement, fallible)); 7832 7833 if (!entry) { 7834 return NS_ERROR_OUT_OF_MEMORY; 7835 } 7836 7837 if (entry->mSubDocument) { 7838 entry->mSubDocument->SetParentDocument(nullptr); 7839 7840 // Release the old sub document 7841 NS_RELEASE(entry->mSubDocument); 7842 } 7843 7844 entry->mSubDocument = aSubDoc; 7845 NS_ADDREF(entry->mSubDocument); 7846 7847 aSubDoc->SetParentDocument(this); 7848 } 7849 7850 return NS_OK; 7851 } 7852 7853 Document* Document::GetSubDocumentFor(nsIContent* aContent) const { 7854 if (mSubDocuments && aContent->IsElement()) { 7855 auto entry = static_cast<SubDocMapEntry*>( 7856 mSubDocuments->Search(aContent->AsElement())); 7857 7858 if (entry) { 7859 return entry->mSubDocument; 7860 } 7861 } 7862 7863 return nullptr; 7864 } 7865 7866 Element* Document::GetEmbedderElement() const { 7867 // We check if we're the active document in our BrowsingContext 7868 // by comparing against its document, rather than checking if the 7869 // WindowContext is cached, since mWindow may be null when we're 7870 // called (such as in nsPresContext::Init). 7871 if (BrowsingContext* bc = GetBrowsingContext()) { 7872 return bc->GetExtantDocument() == this ? bc->GetEmbedderElement() : nullptr; 7873 } 7874 7875 return nullptr; 7876 } 7877 7878 Element* Document::GetRootElement() const { 7879 return (mCachedRootElement && mCachedRootElement->GetParentNode() == this) 7880 ? mCachedRootElement 7881 : GetRootElementInternal(); 7882 } 7883 7884 Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); } 7885 7886 Element* Document::GetRootElementInternal() const { 7887 // We invoke GetRootElement() immediately before the servo traversal, so we 7888 // should always have a cache hit from Servo. 7889 MOZ_ASSERT(NS_IsMainThread()); 7890 7891 // Loop backwards because any non-elements, such as doctypes and PIs 7892 // are likely to appear before the root element. 7893 for (nsIContent* child = GetLastChild(); child; 7894 child = child->GetPreviousSibling()) { 7895 if (Element* element = Element::FromNode(child)) { 7896 const_cast<Document*>(this)->mCachedRootElement = element; 7897 return element; 7898 } 7899 } 7900 7901 const_cast<Document*>(this)->mCachedRootElement = nullptr; 7902 return nullptr; 7903 } 7904 7905 void Document::InsertChildBefore( 7906 nsIContent* aKid, nsIContent* aBeforeThis, bool aNotify, ErrorResult& aRv, 7907 nsINode* aOldParent, MutationEffectOnScript aMutationEffectOnScript) { 7908 const bool isElementInsertion = aKid->IsElement(); 7909 if (isElementInsertion && GetRootElement()) { 7910 NS_WARNING("Inserting root element when we already have one"); 7911 aRv.ThrowHierarchyRequestError("There is already a root element."); 7912 return; 7913 } 7914 7915 nsINode::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv, aOldParent, 7916 aMutationEffectOnScript); 7917 if (isElementInsertion && !aRv.Failed()) { 7918 CreateCustomContentContainerIfNeeded(); 7919 } 7920 } 7921 7922 void Document::RemoveChildNode(nsIContent* aKid, bool aNotify, 7923 const BatchRemovalState* aState, 7924 nsINode* aNewParent, 7925 MutationEffectOnScript aMutationEffectOnScript) { 7926 Maybe<mozAutoDocUpdate> updateBatch; 7927 const bool removingRoot = aKid->IsElement(); 7928 if (removingRoot) { 7929 updateBatch.emplace(this, aNotify); 7930 7931 WillRemoveRoot(); 7932 7933 // Notify early so that we can clear the cached element after notifying, 7934 // without having to slow down nsINode::RemoveChildNode. 7935 if (aNotify) { 7936 ContentRemoveInfo info; 7937 info.mBatchRemovalState = aState; 7938 info.mNewParent = aNewParent; 7939 MutationObservers::NotifyContentWillBeRemoved(this, aKid, info); 7940 aNotify = false; 7941 } 7942 7943 // Preemptively clear mCachedRootElement, since we are about to remove it 7944 // from our child list, and we don't want to return this maybe-obsolete 7945 // value from any GetRootElement() calls that happen inside of 7946 // RemoveChildNode(). 7947 // (NOTE: for this to be useful, RemoveChildNode() must NOT trigger any 7948 // GetRootElement() calls until after it's removed the child from mChildren. 7949 // Any call before that point would restore this soon-to-be-obsolete cached 7950 // answer, and our clearing here would be fruitless.) 7951 mCachedRootElement = nullptr; 7952 } 7953 7954 nsINode::RemoveChildNode(aKid, aNotify, nullptr, aNewParent, 7955 aMutationEffectOnScript); 7956 MOZ_ASSERT(mCachedRootElement != aKid, 7957 "Stale pointer in mCachedRootElement, after we tried to clear it " 7958 "(maybe somebody called GetRootElement() too early?)"); 7959 } 7960 7961 void Document::AddStyleSheetToStyleSets(StyleSheet& aSheet) { 7962 if (mStyleSetFilled) { 7963 EnsureStyleSet().AddDocStyleSheet(aSheet); 7964 ApplicableStylesChanged(); 7965 } 7966 } 7967 7968 void Document::RecordShadowStyleChange(ShadowRoot& aShadowRoot) { 7969 EnsureStyleSet().RecordShadowStyleChange(aShadowRoot); 7970 ApplicableStylesChanged(/* aKnownInShadowTree= */ true); 7971 } 7972 7973 void Document::ApplicableStylesChanged(bool aKnownInShadowTree) { 7974 // TODO(emilio): if we decide to resolve style in display: none iframes, then 7975 // we need to always track style changes and remove the mStyleSetFilled. 7976 if (!mStyleSetFilled) { 7977 return; 7978 } 7979 if (!aKnownInShadowTree) { 7980 MarkUserFontSetDirty(); 7981 } 7982 PresShell* ps = GetPresShell(); 7983 if (!ps) { 7984 return; 7985 } 7986 7987 ps->EnsureStyleFlush(); 7988 nsPresContext* pc = ps->GetPresContext(); 7989 if (!pc) { 7990 return; 7991 } 7992 7993 if (!aKnownInShadowTree) { 7994 pc->MarkCounterStylesDirty(); 7995 pc->MarkFontFeatureValuesDirty(); 7996 pc->MarkFontPaletteValuesDirty(); 7997 } 7998 pc->RestyleManager()->NextRestyleIsForCSSRuleChanges(); 7999 } 8000 8001 void Document::RemoveStyleSheetFromStyleSets(StyleSheet& aSheet) { 8002 if (mStyleSetFilled) { 8003 mStyleSet->RemoveStyleSheet(aSheet); 8004 ApplicableStylesChanged(); 8005 } 8006 } 8007 8008 void Document::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) { 8009 DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet); 8010 8011 if (aSheet.IsApplicable()) { 8012 AddStyleSheetToStyleSets(aSheet); 8013 } 8014 } 8015 8016 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) { 8017 if (!aSheet.IsDirectlyAssociatedTo(*this)) { 8018 return; 8019 } 8020 if (aSheet.IsApplicable()) { 8021 AddStyleSheetToStyleSets(aSheet); 8022 } else { 8023 RemoveStyleSheetFromStyleSets(aSheet); 8024 } 8025 } 8026 8027 void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) { 8028 if (!StyleSheetChangeEventsEnabled()) { 8029 return; 8030 } 8031 8032 StyleSheetApplicableStateChangeEventInit init; 8033 init.mBubbles = true; 8034 init.mCancelable = true; 8035 init.mStylesheet = &aSheet; 8036 init.mApplicable = aSheet.IsApplicable(); 8037 8038 RefPtr<StyleSheetApplicableStateChangeEvent> event = 8039 StyleSheetApplicableStateChangeEvent::Constructor( 8040 this, u"StyleSheetApplicableStateChanged"_ns, init); 8041 event->SetTrusted(true); 8042 event->SetTarget(this); 8043 RefPtr<AsyncEventDispatcher> asyncDispatcher = 8044 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes); 8045 asyncDispatcher->PostDOMEvent(); 8046 } 8047 8048 void Document::PostStyleSheetRemovedEvent(StyleSheet& aSheet) { 8049 if (!StyleSheetChangeEventsEnabled()) { 8050 return; 8051 } 8052 8053 StyleSheetRemovedEventInit init; 8054 init.mBubbles = true; 8055 init.mCancelable = false; 8056 init.mStylesheet = &aSheet; 8057 8058 RefPtr<StyleSheetRemovedEvent> event = 8059 StyleSheetRemovedEvent::Constructor(this, u"StyleSheetRemoved"_ns, init); 8060 event->SetTrusted(true); 8061 event->SetTarget(this); 8062 RefPtr<AsyncEventDispatcher> asyncDispatcher = 8063 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes); 8064 asyncDispatcher->PostDOMEvent(); 8065 } 8066 8067 void Document::PostCustomPropertyRegistered( 8068 const PropertyDefinition& aDefinition) { 8069 if (!StyleSheetChangeEventsEnabled()) { 8070 return; 8071 } 8072 8073 CSSCustomPropertyRegisteredEventInit init; 8074 init.mBubbles = true; 8075 init.mCancelable = false; 8076 8077 InspectorCSSPropertyDefinition property; 8078 8079 property.mName.Append(aDefinition.mName); 8080 property.mSyntax.Append(aDefinition.mSyntax); 8081 property.mInherits = aDefinition.mInherits; 8082 if (aDefinition.mInitialValue.WasPassed()) { 8083 property.mInitialValue.Append(aDefinition.mInitialValue.Value()); 8084 } else { 8085 property.mInitialValue.SetIsVoid(true); 8086 } 8087 property.mFromJS = true; 8088 init.mPropertyDefinition = property; 8089 8090 RefPtr<CSSCustomPropertyRegisteredEvent> event = 8091 CSSCustomPropertyRegisteredEvent::Constructor( 8092 this, u"csscustompropertyregistered"_ns, init); 8093 event->SetTrusted(true); 8094 event->SetTarget(this); 8095 RefPtr<AsyncEventDispatcher> asyncDispatcher = 8096 new AsyncEventDispatcher(this, event.forget(), ChromeOnlyDispatch::eYes); 8097 asyncDispatcher->PostDOMEvent(); 8098 } 8099 8100 static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets, 8101 nsIURI* aSheetURI) { 8102 for (int32_t i = aSheets.Length() - 1; i >= 0; i--) { 8103 bool bEqual; 8104 nsIURI* uri = aSheets[i]->GetOriginalURI(); 8105 if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual) { 8106 return i; 8107 } 8108 } 8109 8110 return -1; 8111 } 8112 8113 nsresult Document::LoadAdditionalStyleSheet(additionalSheetType aType, 8114 nsIURI* aSheetURI) { 8115 MOZ_ASSERT(aSheetURI, "null arg"); 8116 8117 // Checking if we have loaded this one already. 8118 if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0) { 8119 return NS_ERROR_INVALID_ARG; 8120 } 8121 8122 // Loading the sheet sync. 8123 RefPtr<css::Loader> loader = new css::Loader(GetDocGroup()); 8124 8125 css::SheetParsingMode parsingMode; 8126 switch (aType) { 8127 case Document::eAgentSheet: 8128 parsingMode = css::eAgentSheetFeatures; 8129 break; 8130 8131 case Document::eUserSheet: 8132 parsingMode = css::eUserSheetFeatures; 8133 break; 8134 8135 case Document::eAuthorSheet: 8136 parsingMode = css::eAuthorSheetFeatures; 8137 break; 8138 8139 default: 8140 MOZ_CRASH("impossible value for aType"); 8141 } 8142 8143 auto result = loader->LoadSheetSync(aSheetURI, parsingMode, 8144 css::Loader::UseSystemPrincipal::Yes); 8145 if (result.isErr()) { 8146 return result.unwrapErr(); 8147 } 8148 8149 RefPtr<StyleSheet> sheet = result.unwrap(); 8150 8151 MOZ_ASSERT(sheet->IsApplicable()); 8152 8153 return AddAdditionalStyleSheet(aType, sheet); 8154 } 8155 8156 nsresult Document::AddAdditionalStyleSheet(additionalSheetType aType, 8157 StyleSheet* aSheet) { 8158 if (mAdditionalSheets[aType].Contains(aSheet)) { 8159 return NS_ERROR_INVALID_ARG; 8160 } 8161 8162 if (!aSheet->IsApplicable()) { 8163 return NS_ERROR_INVALID_ARG; 8164 } 8165 8166 if (NS_WARN_IF(aSheet->GetAssociatedDocumentOrShadowRoot())) { 8167 return NS_ERROR_INVALID_ARG; 8168 } 8169 8170 mAdditionalSheets[aType].AppendElement(aSheet); 8171 8172 if (mStyleSetFilled) { 8173 EnsureStyleSet().AppendStyleSheet(*aSheet); 8174 ApplicableStylesChanged(); 8175 } 8176 return NS_OK; 8177 } 8178 8179 void Document::RemoveAdditionalStyleSheet(additionalSheetType aType, 8180 nsIURI* aSheetURI) { 8181 MOZ_ASSERT(aSheetURI); 8182 8183 nsTArray<RefPtr<StyleSheet>>& sheets = mAdditionalSheets[aType]; 8184 8185 int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI); 8186 if (i >= 0) { 8187 RefPtr<StyleSheet> sheetRef = std::move(sheets[i]); 8188 sheets.RemoveElementAt(i); 8189 8190 if (!mIsGoingAway) { 8191 MOZ_ASSERT(sheetRef->IsApplicable()); 8192 if (mStyleSetFilled) { 8193 EnsureStyleSet().RemoveStyleSheet(*sheetRef); 8194 ApplicableStylesChanged(); 8195 } 8196 } 8197 sheetRef->ClearAssociatedDocumentOrShadowRoot(); 8198 } 8199 } 8200 8201 void Document::CreateCSSAndStyleImageLoaders(bool aLazy) { 8202 if (aLazy) { 8203 PROFILER_MARKER_UNTYPED("LazyCreateCSSAndStyleImageLoaders", DOM, 8204 MarkerStack::Capture()); 8205 } 8206 mStyleImageLoader = new css::ImageLoader(this); 8207 mCSSLoader = new css::Loader(this); 8208 mCSSLoader->SetCompatibilityMode(mCompatMode); 8209 } 8210 8211 nsIGlobalObject* Document::GetScopeObject() const { 8212 nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject)); 8213 return scope; 8214 } 8215 8216 DocGroup* Document::GetDocGroupOrCreate() { 8217 if (!mDocGroup && GetBrowsingContext()) { 8218 BrowsingContextGroup* group = GetBrowsingContext()->Group(); 8219 MOZ_ASSERT(group); 8220 8221 mDocGroup = group->AddDocument(this); 8222 } 8223 return mDocGroup; 8224 } 8225 8226 void Document::SetScopeObject(nsIGlobalObject* aGlobal) { 8227 mScopeObject = do_GetWeakReference(aGlobal); 8228 if (aGlobal) { 8229 mHasHadScriptHandlingObject = true; 8230 8231 nsPIDOMWindowInner* window = aGlobal->GetAsInnerWindow(); 8232 if (!window) { 8233 return; 8234 } 8235 8236 // Attempt to join a DocGroup based on our global and container now that our 8237 // principal is locked in (due to being added to a window). 8238 DocGroup* docGroup = GetDocGroupOrCreate(); 8239 if (!docGroup) { 8240 // If we failed to join a DocGroup, inherit from our parent window. 8241 // 8242 // NOTE: It is possible for Document to be cross-origin to window while 8243 // loading a cross-origin data document over XHR with CORS. In that case, 8244 // the DocGroup can have a key which does not match NodePrincipal(). 8245 MOZ_ASSERT(!mDocumentContainer, 8246 "Must have DocGroup if loaded in a DocShell"); 8247 mDocGroup = window->GetDocGroup(); 8248 mDocGroup->AddDocument(this); 8249 } 8250 8251 #ifdef DEBUG 8252 AssertDocGroupMatchesKey(); 8253 #endif 8254 MOZ_ASSERT_IF( 8255 mNodeInfoManager->GetArenaAllocator(), 8256 mNodeInfoManager->GetArenaAllocator() == mDocGroup->ArenaAllocator()); 8257 } 8258 } 8259 8260 bool Document::ContainsEMEContent() { 8261 nsPIDOMWindowInner* win = GetInnerWindow(); 8262 // Note this case is different from checking just media elements in that 8263 // it covers when we've created MediaKeys but not associated them with a 8264 // media element. 8265 return win && win->HasActiveMediaKeysInstance(); 8266 } 8267 8268 bool Document::ContainsMSEContent() { 8269 bool containsMSE = false; 8270 EnumerateActivityObservers([&containsMSE](nsISupports* aSupports) { 8271 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports)); 8272 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) { 8273 RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject(); 8274 if (ms) { 8275 containsMSE = true; 8276 } 8277 } 8278 }); 8279 return containsMSE; 8280 } 8281 8282 static void NotifyActivityChangedCallback(nsISupports* aSupports) { 8283 nsCOMPtr<nsIContent> content(do_QueryInterface(aSupports)); 8284 if (auto* mediaElem = HTMLMediaElement::FromNodeOrNull(content)) { 8285 mediaElem->NotifyOwnerDocumentActivityChanged(); 8286 } 8287 nsCOMPtr<nsIDocumentActivity> objectDocumentActivity( 8288 do_QueryInterface(aSupports)); 8289 if (objectDocumentActivity) { 8290 objectDocumentActivity->NotifyOwnerDocumentActivityChanged(); 8291 } else { 8292 nsCOMPtr<nsIImageLoadingContent> imageLoadingContent( 8293 do_QueryInterface(aSupports)); 8294 if (imageLoadingContent) { 8295 auto* ilc = 8296 static_cast<nsImageLoadingContent*>(imageLoadingContent.get()); 8297 ilc->NotifyOwnerDocumentActivityChanged(); 8298 } 8299 } 8300 } 8301 8302 void Document::NotifyActivityChanged() { 8303 EnumerateActivityObservers(NotifyActivityChangedCallback); 8304 // https://w3c.github.io/screen-wake-lock/#handling-document-loss-of-full-activity 8305 if (!IsActive()) { 8306 UnlockAllWakeLocks(WakeLockType::Screen); 8307 } 8308 } 8309 8310 void Document::SetContainer(nsDocShell* aContainer) { 8311 if (aContainer) { 8312 mDocumentContainer = aContainer; 8313 } else { 8314 mDocumentContainer = WeakPtr<nsDocShell>(); 8315 } 8316 8317 mInChromeDocShell = 8318 aContainer && aContainer->GetBrowsingContext()->IsChrome(); 8319 8320 NotifyActivityChanged(); 8321 8322 // IsTopLevelWindowInactive depends on the docshell, so 8323 // update the cached value now that it's available. 8324 UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, false); 8325 if (!aContainer) { 8326 return; 8327 } 8328 8329 BrowsingContext* context = aContainer->GetBrowsingContext(); 8330 MOZ_ASSERT_IF(context && mDocGroup, 8331 context->Group() == mDocGroup->GetBrowsingContextGroup()); 8332 if (context && context->IsContent()) { 8333 SetIsTopLevelContentDocument(context->IsTopContent()); 8334 SetIsContentDocument(true); 8335 } else { 8336 SetIsTopLevelContentDocument(false); 8337 SetIsContentDocument(false); 8338 } 8339 } 8340 8341 nsISupports* Document::GetContainer() const { 8342 return static_cast<nsIDocShell*>(mDocumentContainer); 8343 } 8344 8345 void Document::SetScriptGlobalObject( 8346 nsIScriptGlobalObject* aScriptGlobalObject) { 8347 MOZ_ASSERT(aScriptGlobalObject || !mAnimationController || 8348 mAnimationController->IsPausedByType( 8349 SMILTimeContainer::PAUSE_PAGEHIDE | 8350 SMILTimeContainer::PAUSE_BEGIN), 8351 "Clearing window pointer while animations are unpaused"); 8352 8353 if (mScriptGlobalObject && !aScriptGlobalObject) { 8354 // We're detaching from the window. We need to grab a pointer to 8355 // our layout history state now. 8356 mLayoutHistoryState = GetLayoutHistoryState(); 8357 8358 // Also make sure to remove our onload blocker now if we haven't done it yet 8359 if (mOnloadBlockCount != 0) { 8360 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup(); 8361 if (loadGroup) { 8362 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK); 8363 } 8364 } 8365 8366 if (GetController().isSome()) { 8367 if (imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this)) { 8368 loader->ClearCacheForControlledDocument(this); 8369 } 8370 8371 // We may become controlled again if this document comes back out 8372 // of bfcache. Clear our state to allow that to happen. Only 8373 // clear this flag if we are actually controlled, though, so pages 8374 // that were force reloaded don't become controlled when they 8375 // come out of bfcache. 8376 mMaybeServiceWorkerControlled = false; 8377 } 8378 8379 if (GetWindowContext()) { 8380 // The document is about to lose its window, so this is a good time to 8381 // send our page use counters, while we still have access to our 8382 // WindowContext. 8383 // 8384 // (We also do this in nsGlobalWindowInner::FreeInnerObjects(), which 8385 // catches some cases of documents losing their window that don't 8386 // get in here.) 8387 SendPageUseCounters(); 8388 } 8389 } 8390 8391 // BlockOnload() might be called before mScriptGlobalObject is set. 8392 // We may need to add the blocker once mScriptGlobalObject is set. 8393 bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject; 8394 8395 mScriptGlobalObject = aScriptGlobalObject; 8396 8397 if (needOnloadBlocker) { 8398 EnsureOnloadBlocker(); 8399 } 8400 8401 // FIXME(emilio): is this really needed? 8402 MaybeScheduleFrameRequestCallbacks(); 8403 8404 if (aScriptGlobalObject) { 8405 // Go back to using the docshell for the layout history state 8406 mLayoutHistoryState = nullptr; 8407 SetScopeObject(aScriptGlobalObject); 8408 mHasHadDefaultView = true; 8409 8410 if (mAllowDNSPrefetch) { 8411 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer); 8412 if (docShell) { 8413 #ifdef DEBUG 8414 nsCOMPtr<nsIWebNavigation> webNav = 8415 do_GetInterface(aScriptGlobalObject); 8416 NS_ASSERTION(SameCOMIdentity(webNav, docShell), 8417 "Unexpected container or script global?"); 8418 #endif 8419 bool allowDNSPrefetch; 8420 docShell->GetAllowDNSPrefetch(&allowDNSPrefetch); 8421 mAllowDNSPrefetch = allowDNSPrefetch; 8422 } 8423 } 8424 8425 // If we are set in a window that is already focused we should remember this 8426 // as the time the document gained focus. 8427 if (HasFocus(IgnoreErrors())) { 8428 SetLastFocusTime(TimeStamp::Now()); 8429 } 8430 } 8431 8432 // Remember the pointer to our window (or lack there of), to avoid 8433 // having to QI every time it's asked for. 8434 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptGlobalObject); 8435 mWindow = window; 8436 8437 if (mReadyState != READYSTATE_COMPLETE) { 8438 if (auto* wgc = GetWindowGlobalChild()) { 8439 // This gets unset on OnPageShow. 8440 wgc->BlockBFCacheFor(BFCacheStatus::PAGE_LOADING); 8441 } 8442 } 8443 8444 // Now that we know what our window is, we can flush the CSP errors to the 8445 // Web Console. We are flushing all messages that occurred and were stored in 8446 // the queue prior to this point. 8447 if (nsIContentSecurityPolicy* csp = 8448 PolicyContainer::GetCSP(mPolicyContainer)) { 8449 nsCSPContext::Cast(csp)->flushConsoleMessages(); 8450 } 8451 8452 nsCOMPtr<nsIHttpChannelInternal> internalChannel = 8453 do_QueryInterface(GetChannel()); 8454 if (internalChannel) { 8455 nsCOMArray<nsISecurityConsoleMessage> messages; 8456 DebugOnly<nsresult> rv = internalChannel->TakeAllSecurityMessages(messages); 8457 MOZ_ASSERT(NS_SUCCEEDED(rv)); 8458 SendToConsole(messages); 8459 } 8460 8461 // Set our visibility state, but do not fire the event. This is correct 8462 // because either we're coming out of bfcache (in which case IsVisible() will 8463 // still test false at this point and no state change will happen) or we're 8464 // doing the initial document load and don't want to fire the event for this 8465 // change. 8466 // 8467 // When the visibility is changed, notify it to observers. 8468 // Some observers need the notification, for example HTMLMediaElement uses 8469 // it to update internal media resource allocation. 8470 // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder 8471 // creation are already done before Document::SetScriptGlobalObject() call. 8472 // MediaDecoder decides whether starting decoding is decided based on 8473 // document's visibility. When the MediaDecoder is created, 8474 // Document::SetScriptGlobalObject() is not yet called and document is 8475 // hidden state. Therefore the MediaDecoder decides that decoding is 8476 // not yet necessary. But soon after Document::SetScriptGlobalObject() 8477 // call, the document becomes not hidden. At the time, MediaDecoder needs 8478 // to know it and needs to start updating decoding. 8479 UpdateVisibilityState(DispatchVisibilityChange::No); 8480 8481 // The global in the template contents owner document should be the same. 8482 if (mTemplateContentsOwner && mTemplateContentsOwner != this) { 8483 mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject); 8484 } 8485 8486 // Tell the script loader about the new global object. 8487 if (mScriptLoader && !IsTemplateContentsOwner()) { 8488 mScriptLoader->SetGlobalObject(mScriptGlobalObject); 8489 } 8490 8491 if (!mMaybeServiceWorkerControlled && mDocumentContainer && 8492 mScriptGlobalObject && GetChannel()) { 8493 // If we are shift-reloaded, don't associate with a ServiceWorker. 8494 if (mDocumentContainer->IsForceReloading()) { 8495 NS_WARNING("Page was shift reloaded, skipping ServiceWorker control"); 8496 return; 8497 } 8498 8499 mMaybeServiceWorkerControlled = true; 8500 } 8501 } 8502 8503 nsIScriptGlobalObject* Document::GetScriptHandlingObjectInternal() const { 8504 MOZ_ASSERT(!mScriptGlobalObject, 8505 "Do not call this when mScriptGlobalObject is set!"); 8506 if (mHasHadDefaultView) { 8507 return nullptr; 8508 } 8509 8510 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject = 8511 do_QueryReferent(mScopeObject); 8512 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(scriptHandlingObject); 8513 if (win) { 8514 nsPIDOMWindowOuter* outer = win->GetOuterWindow(); 8515 if (!outer || outer->GetCurrentInnerWindow() != win) { 8516 NS_WARNING("Wrong inner/outer window combination!"); 8517 return nullptr; 8518 } 8519 } 8520 return scriptHandlingObject; 8521 } 8522 void Document::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) { 8523 NS_ASSERTION(!mScriptGlobalObject || mScriptGlobalObject == aScriptObject, 8524 "Wrong script object!"); 8525 if (aScriptObject) { 8526 SetScopeObject(aScriptObject); 8527 mHasHadDefaultView = false; 8528 } 8529 } 8530 8531 nsPIDOMWindowOuter* Document::GetWindowInternal() const { 8532 MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!"); 8533 // Let's use mScriptGlobalObject. Even if the document is already removed from 8534 // the docshell, the outer window might be still obtainable from the it. 8535 nsCOMPtr<nsPIDOMWindowOuter> win; 8536 if (mRemovedFromDocShell) { 8537 // The docshell returns the outer window we are done. 8538 nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocumentContainer); 8539 if (kungFuDeathGrip) { 8540 win = kungFuDeathGrip->GetWindow(); 8541 } 8542 } else { 8543 if (nsCOMPtr<nsPIDOMWindowInner> inner = 8544 do_QueryInterface(mScriptGlobalObject)) { 8545 // mScriptGlobalObject is always the inner window, let's get the outer. 8546 win = inner->GetOuterWindow(); 8547 } 8548 } 8549 8550 return win; 8551 } 8552 8553 bool Document::InternalAllowXULXBL() { 8554 if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) { 8555 mAllowXULXBL = eTriTrue; 8556 return true; 8557 } 8558 8559 mAllowXULXBL = eTriFalse; 8560 return false; 8561 } 8562 8563 // Note: We don't hold a reference to the document observer; we assume 8564 // that it has a live reference to the document. 8565 void Document::AddObserver(nsIDocumentObserver* aObserver) { 8566 NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex, 8567 "Observer already in the list"); 8568 mObservers.AppendElement(aObserver); 8569 AddMutationObserver(aObserver); 8570 } 8571 8572 bool Document::RemoveObserver(nsIDocumentObserver* aObserver) { 8573 // If we're in the process of destroying the document (and we're 8574 // informing the observers of the destruction), don't remove the 8575 // observers from the list. This is not a big deal, since we 8576 // don't hold a live reference to the observers. 8577 if (!mInDestructor) { 8578 RemoveMutationObserver(aObserver); 8579 return mObservers.RemoveElement(aObserver); 8580 } 8581 8582 return mObservers.Contains(aObserver); 8583 } 8584 8585 void Document::BeginUpdate() { 8586 ++mUpdateNestLevel; 8587 nsContentUtils::AddScriptBlocker(); 8588 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this)); 8589 } 8590 8591 void Document::EndUpdate() { 8592 const bool reset = !mPendingMaybeEditingStateChanged; 8593 mPendingMaybeEditingStateChanged = true; 8594 8595 NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this)); 8596 8597 --mUpdateNestLevel; 8598 8599 nsContentUtils::RemoveScriptBlocker(); 8600 8601 if (mXULBroadcastManager) { 8602 mXULBroadcastManager->MaybeBroadcast(); 8603 } 8604 8605 if (reset) { 8606 mPendingMaybeEditingStateChanged = false; 8607 } 8608 MaybeEditingStateChanged(); 8609 } 8610 8611 void Document::BeginLoad() { 8612 if (IsEditingOn()) { 8613 // Reset() blows away all event listeners in the document, and our 8614 // editor relies heavily on those. Midas is turned on, to make it 8615 // work, re-initialize it to give it a chance to add its event 8616 // listeners again. 8617 8618 TurnEditingOff(); 8619 EditingStateChanged(); 8620 } 8621 8622 MOZ_ASSERT(!mDidCallBeginLoad); 8623 mDidCallBeginLoad = true; 8624 8625 // Block onload here to prevent having to deal with blocking and 8626 // unblocking it while we know the document is loading. 8627 BlockOnload(); 8628 mDidFireDOMContentLoaded = false; 8629 BlockDOMContentLoaded(); 8630 8631 if (mScriptLoader && !IsInitialDocument()) { 8632 mScriptLoader->BeginDeferringScripts(); 8633 } 8634 8635 NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this)); 8636 } 8637 8638 void Document::MozSetImageElement(const nsAString& aImageElementId, 8639 Element* aElement) { 8640 if (aImageElementId.IsEmpty()) return; 8641 8642 // Hold a script blocker while calling SetImageElement since that can call 8643 // out to id-observers 8644 nsAutoScriptBlocker scriptBlocker; 8645 8646 IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aImageElementId); 8647 if (entry) { 8648 entry->SetImageElement(aElement); 8649 if (entry->IsEmpty()) { 8650 mIdentifierMap.RemoveEntry(entry); 8651 } 8652 } 8653 } 8654 8655 void Document::DispatchContentLoadedEvents() { 8656 // If you add early returns from this method, make sure you're 8657 // calling UnblockOnload properly. 8658 8659 // Unpin references to preloaded images 8660 mPreloadingImages.Clear(); 8661 8662 // DOM manipulation after content loaded should not care if the element 8663 // came from the preloader. 8664 mPreloadedPreconnects.Clear(); 8665 8666 if (mTiming) { 8667 mTiming->NotifyDOMContentLoadedStart(Document::GetDocumentURI()); 8668 } 8669 8670 // Dispatch observer notification to notify observers document is interactive. 8671 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 8672 if (os) { 8673 nsIPrincipal* principal = NodePrincipal(); 8674 os->NotifyObservers(ToSupports(this), 8675 principal->IsSystemPrincipal() 8676 ? "chrome-document-interactive" 8677 : "content-document-interactive", 8678 nullptr); 8679 } 8680 8681 // Fire a DOM event notifying listeners that this document has been 8682 // loaded (excluding images and other loads initiated by this 8683 // document). 8684 nsContentUtils::DispatchTrustedEvent(this, this, u"DOMContentLoaded"_ns, 8685 CanBubble::eYes, Cancelable::eNo); 8686 8687 if (auto* const window = GetInnerWindow()) { 8688 const RefPtr<ServiceWorkerContainer> serviceWorker = 8689 window->Navigator()->ServiceWorker(); 8690 8691 // This could cause queued messages from a service worker to get 8692 // dispatched on serviceWorker. 8693 serviceWorker->StartMessages(); 8694 } 8695 8696 if (MayStartLayout()) { 8697 MaybeResolveReadyForIdle(); 8698 } 8699 8700 if (mTiming) { 8701 mTiming->NotifyDOMContentLoadedEnd(Document::GetDocumentURI()); 8702 } 8703 8704 // If this document is a [i]frame, fire a DOMFrameContentLoaded 8705 // event on all parent documents notifying that the HTML (excluding 8706 // other external files such as images and stylesheets) in a frame 8707 // has finished loading. 8708 8709 // target_frame is the [i]frame element that will be used as the 8710 // target for the event. It's the [i]frame whose content is done 8711 // loading. 8712 nsCOMPtr<Element> target_frame = GetEmbedderElement(); 8713 8714 if (target_frame && target_frame->IsInComposedDoc()) { 8715 nsCOMPtr<Document> parent = target_frame->OwnerDoc(); 8716 while (parent) { 8717 RefPtr<Event> event; 8718 if (parent) { 8719 IgnoredErrorResult ignored; 8720 event = parent->CreateEvent(u"Events"_ns, CallerType::System, ignored); 8721 } 8722 8723 if (event) { 8724 event->InitEvent(u"DOMFrameContentLoaded"_ns, true, true); 8725 8726 event->SetTarget(target_frame); 8727 event->SetTrusted(true); 8728 8729 // To dispatch this event we must manually call 8730 // EventDispatcher::Dispatch() on the ancestor document since the 8731 // target is not in the same document, so the event would never reach 8732 // the ancestor document if we used the normal event 8733 // dispatching code. 8734 8735 WidgetEvent* innerEvent = event->WidgetEventPtr(); 8736 if (innerEvent) { 8737 nsEventStatus status = nsEventStatus_eIgnore; 8738 8739 if (RefPtr<nsPresContext> context = parent->GetPresContext()) { 8740 EventDispatcher::Dispatch(parent, context, innerEvent, event, 8741 &status); 8742 } 8743 } 8744 } 8745 8746 parent = parent->GetInProcessParentDocument(); 8747 } 8748 } 8749 8750 nsPIDOMWindowInner* inner = GetInnerWindow(); 8751 if (inner) { 8752 inner->NoteDOMContentLoaded(); 8753 } 8754 8755 // TODO 8756 if (mMaybeServiceWorkerControlled) { 8757 using mozilla::dom::ServiceWorkerManager; 8758 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); 8759 if (swm) { 8760 Maybe<ClientInfo> clientInfo = GetClientInfo(); 8761 if (clientInfo.isSome()) { 8762 swm->MaybeCheckNavigationUpdate(clientInfo.ref()); 8763 } 8764 } 8765 } 8766 8767 if (mSetCompleteAfterDOMContentLoaded) { 8768 SetReadyStateInternal(ReadyState::READYSTATE_COMPLETE); 8769 mSetCompleteAfterDOMContentLoaded = false; 8770 } 8771 8772 UnblockOnload(true); 8773 } 8774 8775 void Document::EndLoad() { 8776 bool turnOnEditing = 8777 mParser && (IsInDesignMode() || mContentEditableCount > 0); 8778 8779 #if defined(DEBUG) 8780 // only assert if nothing stopped the load on purpose 8781 if (!mParserAborted) { 8782 nsContentSecurityUtils::AssertAboutPageHasCSP(this); 8783 nsContentSecurityUtils::AssertChromePageHasCSP(this); 8784 } 8785 #endif 8786 8787 // EndLoad may have been called without a matching call to BeginLoad, in the 8788 // case of a failed parse (for example, due to timeout). In such a case, we 8789 // still want to execute part of this code to do appropriate cleanup, but we 8790 // gate part of it because it is intended to match 1-for-1 with calls to 8791 // BeginLoad. We have an explicit flag bit for this purpose, since it's 8792 // complicated and error prone to derive this condition from other related 8793 // flags that can be manipulated outside of a BeginLoad/EndLoad pair. 8794 8795 // Part 1: Code that always executes to cleanup end of parsing, whether 8796 // that parsing was successful or not. 8797 8798 // Drop the ref to our parser, if any, but keep hold of the sink so that we 8799 // can flush it from FlushPendingNotifications as needed. We might have to 8800 // do that to get a StartLayout() to happen. 8801 if (mParser) { 8802 mWeakSink = do_GetWeakReference(mParser->GetContentSink()); 8803 mParser = nullptr; 8804 } 8805 8806 // Update the attributes on the PerformanceNavigationTiming before notifying 8807 // the onload observers. 8808 if (nsPIDOMWindowInner* window = GetInnerWindow()) { 8809 if (RefPtr<Performance> performance = window->GetPerformance()) { 8810 performance->UpdateNavigationTimingEntry(); 8811 } 8812 } 8813 8814 NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this)); 8815 8816 // Part 2: Code that only executes when this EndLoad matches a BeginLoad. 8817 8818 if (!mDidCallBeginLoad) { 8819 return; 8820 } 8821 mDidCallBeginLoad = false; 8822 8823 UnblockDOMContentLoaded(); 8824 8825 if (turnOnEditing) { 8826 EditingStateChanged(); 8827 } 8828 8829 if (!GetWindow()) { 8830 // This is a document that's not in a window. For example, this could be an 8831 // XMLHttpRequest responseXML document, or a document created via DOMParser 8832 // or DOMImplementation. We don't reach this code normally for such 8833 // documents (which is not obviously correct), but can reach it via 8834 // document.open()/document.close(). 8835 // 8836 // Such documents don't fire load events, but per spec should set their 8837 // readyState to "complete" when parsing and all loading of subresources is 8838 // done. Parsing is done now, and documents not in a window don't load 8839 // subresources, so just go ahead and mark ourselves as complete. 8840 SetReadyStateInternal(Document::READYSTATE_COMPLETE, 8841 /* updateTimingInformation = */ false); 8842 8843 // Reset mSkipLoadEventAfterClose just in case. 8844 mSkipLoadEventAfterClose = false; 8845 } 8846 } 8847 8848 void Document::UnblockDOMContentLoaded() { 8849 MOZ_ASSERT(mBlockDOMContentLoaded); 8850 if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) { 8851 return; 8852 } 8853 8854 MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, 8855 ("DOCUMENT %p UnblockDOMContentLoaded", this)); 8856 8857 mDidFireDOMContentLoaded = true; 8858 8859 MOZ_ASSERT(IsInitialDocument() || mReadyState == READYSTATE_INTERACTIVE); 8860 if (!mSynchronousDOMContentLoaded) { 8861 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 8862 MOZ_ASSERT(!IsInitialDocument()); 8863 nsCOMPtr<nsIRunnable> ev = 8864 NewRunnableMethod("Document::DispatchContentLoadedEvents", this, 8865 &Document::DispatchContentLoadedEvents); 8866 Dispatch(ev.forget()); 8867 } else { 8868 DispatchContentLoadedEvents(); 8869 } 8870 } 8871 8872 void Document::ElementStateChanged(Element* aElement, ElementState aStateMask) { 8873 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), 8874 "Someone forgot a scriptblocker"); 8875 NS_DOCUMENT_NOTIFY_OBSERVERS(ElementStateChanged, 8876 (this, aElement, aStateMask)); 8877 } 8878 8879 void Document::RuleChanged(StyleSheet& aSheet, css::Rule*, 8880 const StyleRuleChange&) { 8881 if (aSheet.IsApplicable()) { 8882 ApplicableStylesChanged(); 8883 } 8884 } 8885 8886 void Document::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) { 8887 if (aRule.IsIncompleteImportRule()) { 8888 return; 8889 } 8890 8891 if (aSheet.IsApplicable()) { 8892 ApplicableStylesChanged(); 8893 } 8894 } 8895 8896 void Document::ImportRuleLoaded(StyleSheet& aSheet) { 8897 if (aSheet.IsApplicable()) { 8898 ApplicableStylesChanged(); 8899 } 8900 } 8901 8902 void Document::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) { 8903 if (aSheet.IsApplicable()) { 8904 ApplicableStylesChanged(); 8905 } 8906 } 8907 8908 static void UnbindAnonymousContent(AnonymousContent& aAnonContent) { 8909 nsCOMPtr<nsINode> parent = aAnonContent.Host()->GetParentNode(); 8910 if (!parent) { 8911 return; 8912 } 8913 MOZ_ASSERT(parent->IsElement()); 8914 MOZ_ASSERT(parent->AsElement()->IsRootOfNativeAnonymousSubtree()); 8915 parent->RemoveChildNode(aAnonContent.Host(), true); 8916 } 8917 8918 static void BindAnonymousContent(AnonymousContent& aAnonContent, 8919 Element& aContainer) { 8920 UnbindAnonymousContent(aAnonContent); 8921 aContainer.AppendChildTo(aAnonContent.Host(), true, IgnoreErrors()); 8922 } 8923 8924 void Document::RemoveCustomContentContainer() { 8925 RefPtr container = std::move(mCustomContentContainer); 8926 if (!container) { 8927 return; 8928 } 8929 nsAutoScriptBlocker scriptBlocker; 8930 if (DevToolsAnonymousAndShadowEventsEnabled()) { 8931 container->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ true); 8932 } 8933 if (PresShell* ps = GetPresShell()) { 8934 ps->ContentWillBeRemoved(container, {}); 8935 } 8936 container->UnbindFromTree(); 8937 } 8938 8939 void Document::CreateCustomContentContainerIfNeeded() { 8940 if (mAnonymousContents.IsEmpty()) { 8941 MOZ_ASSERT(!mCustomContentContainer); 8942 return; 8943 } 8944 if (mCustomContentContainer) { 8945 return; 8946 } 8947 RefPtr root = GetRootElement(); 8948 if (!root) { 8949 // We'll deal with it when we get a root element, if needed. 8950 return; 8951 } 8952 // Create the custom content container. 8953 RefPtr container = CreateHTMLElement(nsGkAtoms::div); 8954 #ifdef DEBUG 8955 // We restyle our mCustomContentContainer, even though it's root anonymous 8956 // content. Normally that's not OK because the frame constructor doesn't know 8957 // how to order the frame tree in such cases, but we make this work for this 8958 // particular case, so it's OK. 8959 container->SetProperty(nsGkAtoms::restylableAnonymousNode, 8960 reinterpret_cast<void*>(true)); 8961 #endif // DEBUG 8962 container->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent, 8963 reinterpret_cast<void*>(true)); 8964 container->SetIsNativeAnonymousRoot(); 8965 // Do not create an accessible object for the container. 8966 container->SetAttr(kNameSpaceID_None, nsGkAtoms::role, u"presentation"_ns, 8967 false); 8968 container->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, 8969 u"moz-custom-content-container"_ns, false); 8970 nsAutoScriptBlocker scriptBlocker; 8971 BindContext context(*root, BindContext::ForNativeAnonymous); 8972 if (NS_WARN_IF(NS_FAILED(container->BindToTree(context, *root)))) { 8973 container->UnbindFromTree(); 8974 return; 8975 } 8976 mCustomContentContainer = container; 8977 if (DevToolsAnonymousAndShadowEventsEnabled()) { 8978 container->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ false); 8979 } 8980 if (PresShell* ps = GetPresShell()) { 8981 ps->ContentAppended(container, {}); 8982 } 8983 for (auto& anonContent : mAnonymousContents) { 8984 BindAnonymousContent(*anonContent, *container); 8985 } 8986 } 8987 8988 already_AddRefed<AnonymousContent> Document::InsertAnonymousContent( 8989 ErrorResult& aRv) { 8990 RefPtr<AnonymousContent> anonContent = AnonymousContent::Create(*this); 8991 if (!anonContent) { 8992 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 8993 return nullptr; 8994 } 8995 mAnonymousContents.AppendElement(anonContent); 8996 if (RefPtr container = mCustomContentContainer) { 8997 BindAnonymousContent(*anonContent, *container); 8998 } else { 8999 CreateCustomContentContainerIfNeeded(); 9000 } 9001 return anonContent.forget(); 9002 } 9003 9004 void Document::RemoveAnonymousContent(AnonymousContent& aContent) { 9005 nsAutoScriptBlocker scriptBlocker; 9006 9007 auto index = mAnonymousContents.IndexOf(&aContent); 9008 if (index == mAnonymousContents.NoIndex) { 9009 return; 9010 } 9011 9012 mAnonymousContents.RemoveElementAt(index); 9013 UnbindAnonymousContent(aContent); 9014 9015 if (mAnonymousContents.IsEmpty()) { 9016 RemoveCustomContentContainer(); 9017 } 9018 } 9019 9020 Maybe<ClientInfo> Document::GetClientInfo() const { 9021 if (const Document* orig = GetOriginalDocument()) { 9022 if (Maybe<ClientInfo> info = orig->GetClientInfo()) { 9023 return info; 9024 } 9025 } 9026 9027 if (nsPIDOMWindowInner* inner = GetInnerWindow()) { 9028 return inner->GetClientInfo(); 9029 } 9030 9031 return Maybe<ClientInfo>(); 9032 } 9033 9034 Maybe<ClientState> Document::GetClientState() const { 9035 if (const Document* orig = GetOriginalDocument()) { 9036 if (Maybe<ClientState> state = orig->GetClientState()) { 9037 return state; 9038 } 9039 } 9040 9041 if (nsPIDOMWindowInner* inner = GetInnerWindow()) { 9042 return inner->GetClientState(); 9043 } 9044 9045 return Maybe<ClientState>(); 9046 } 9047 9048 Maybe<ServiceWorkerDescriptor> Document::GetController() const { 9049 if (const Document* orig = GetOriginalDocument()) { 9050 if (Maybe<ServiceWorkerDescriptor> controller = orig->GetController()) { 9051 return controller; 9052 } 9053 } 9054 9055 if (nsPIDOMWindowInner* inner = GetInnerWindow()) { 9056 return inner->GetController(); 9057 } 9058 9059 return Maybe<ServiceWorkerDescriptor>(); 9060 } 9061 9062 // 9063 // Document interface 9064 // 9065 DocumentType* Document::GetDoctype() const { 9066 for (nsIContent* child = GetFirstChild(); child; 9067 child = child->GetNextSibling()) { 9068 if (child->NodeType() == DOCUMENT_TYPE_NODE) { 9069 return static_cast<DocumentType*>(child); 9070 } 9071 } 9072 return nullptr; 9073 } 9074 9075 DOMImplementation* Document::GetImplementation(ErrorResult& rv) { 9076 if (!mDOMImplementation) { 9077 nsCOMPtr<nsIURI> uri; 9078 NS_NewURI(getter_AddRefs(uri), "about:blank"); 9079 if (!uri) { 9080 rv.Throw(NS_ERROR_OUT_OF_MEMORY); 9081 return nullptr; 9082 } 9083 bool hasHadScriptObject = true; 9084 nsIScriptGlobalObject* scriptObject = 9085 GetScriptHandlingObject(hasHadScriptObject); 9086 if (!scriptObject && hasHadScriptObject) { 9087 rv.Throw(NS_ERROR_UNEXPECTED); 9088 return nullptr; 9089 } 9090 mDOMImplementation = new DOMImplementation( 9091 this, scriptObject ? scriptObject : GetScopeObject(), uri, uri); 9092 } 9093 9094 return mDOMImplementation; 9095 } 9096 9097 bool IsLowercaseASCII(const nsAString& aValue) { 9098 int32_t len = aValue.Length(); 9099 for (int32_t i = 0; i < len; ++i) { 9100 char16_t c = aValue[i]; 9101 if (!(0x0061 <= (c) && ((c) <= 0x007a))) { 9102 return false; 9103 } 9104 } 9105 return true; 9106 } 9107 9108 already_AddRefed<Element> Document::CreateElement( 9109 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions, 9110 ErrorResult& rv) { 9111 rv = nsContentUtils::CheckQName(aTagName, false); 9112 if (rv.Failed()) { 9113 return nullptr; 9114 } 9115 9116 bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName); 9117 nsAutoString lcTagName; 9118 if (needsLowercase) { 9119 nsContentUtils::ASCIIToLower(aTagName, lcTagName); 9120 } 9121 9122 const nsString* is = nullptr; 9123 PseudoStyleType pseudoType = PseudoStyleType::NotPseudo; 9124 if (aOptions.IsElementCreationOptions()) { 9125 const ElementCreationOptions& options = 9126 aOptions.GetAsElementCreationOptions(); 9127 9128 if (options.mIs.WasPassed()) { 9129 is = &options.mIs.Value(); 9130 } 9131 9132 // Check 'pseudo' and throw an exception if it's not one allowed 9133 // with CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC. 9134 if (options.mPseudo.WasPassed()) { 9135 Maybe<PseudoStyleRequest> request = 9136 nsCSSPseudoElements::ParsePseudoElement(options.mPseudo.Value()); 9137 if (!request || request->IsNotPseudo() || 9138 !nsCSSPseudoElements::PseudoElementIsJSCreatedNAC(request->mType)) { 9139 rv.ThrowNotSupportedError("Invalid pseudo-element"); 9140 return nullptr; 9141 } 9142 pseudoType = request->mType; 9143 } 9144 } 9145 9146 RefPtr<Element> elem = CreateElem(needsLowercase ? lcTagName : aTagName, 9147 nullptr, mDefaultElementType, is); 9148 9149 if (pseudoType != PseudoStyleType::NotPseudo) { 9150 elem->SetPseudoElementType(pseudoType); 9151 } 9152 9153 return elem.forget(); 9154 } 9155 9156 already_AddRefed<Element> Document::CreateElementNS( 9157 const nsAString& aNamespaceURI, const nsAString& aQualifiedName, 9158 const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) { 9159 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 9160 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName, 9161 mNodeInfoManager, ELEMENT_NODE, 9162 getter_AddRefs(nodeInfo)); 9163 if (rv.Failed()) { 9164 return nullptr; 9165 } 9166 9167 const nsString* is = nullptr; 9168 if (aOptions.IsElementCreationOptions()) { 9169 const ElementCreationOptions& options = 9170 aOptions.GetAsElementCreationOptions(); 9171 if (options.mIs.WasPassed()) { 9172 is = &options.mIs.Value(); 9173 } 9174 } 9175 9176 nsCOMPtr<Element> element; 9177 rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(), 9178 NOT_FROM_PARSER, is); 9179 if (rv.Failed()) { 9180 return nullptr; 9181 } 9182 9183 return element.forget(); 9184 } 9185 9186 already_AddRefed<Element> Document::CreateXULElement( 9187 const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions, 9188 ErrorResult& aRv) { 9189 aRv = nsContentUtils::CheckQName(aTagName, false); 9190 if (aRv.Failed()) { 9191 return nullptr; 9192 } 9193 9194 const nsString* is = nullptr; 9195 if (aOptions.IsElementCreationOptions()) { 9196 const ElementCreationOptions& options = 9197 aOptions.GetAsElementCreationOptions(); 9198 if (options.mIs.WasPassed()) { 9199 is = &options.mIs.Value(); 9200 } 9201 } 9202 9203 RefPtr<Element> elem = CreateElem(aTagName, nullptr, kNameSpaceID_XUL, is); 9204 if (!elem) { 9205 aRv.Throw(NS_ERROR_NOT_AVAILABLE); 9206 return nullptr; 9207 } 9208 return elem.forget(); 9209 } 9210 9211 already_AddRefed<nsTextNode> Document::CreateEmptyTextNode() const { 9212 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager); 9213 return text.forget(); 9214 } 9215 9216 already_AddRefed<nsTextNode> Document::CreateTextNode( 9217 const nsAString& aData) const { 9218 RefPtr<nsTextNode> text = new (mNodeInfoManager) nsTextNode(mNodeInfoManager); 9219 // Don't notify; this node is still being created. 9220 text->SetText(aData, false); 9221 return text.forget(); 9222 } 9223 9224 already_AddRefed<DocumentFragment> Document::CreateDocumentFragment() const { 9225 RefPtr<DocumentFragment> frag = 9226 new (mNodeInfoManager) DocumentFragment(mNodeInfoManager); 9227 return frag.forget(); 9228 } 9229 9230 // Unfortunately, bareword "Comment" is ambiguous with some Mac system headers. 9231 already_AddRefed<dom::Comment> Document::CreateComment( 9232 const nsAString& aData) const { 9233 RefPtr<dom::Comment> comment = 9234 new (mNodeInfoManager) dom::Comment(mNodeInfoManager); 9235 9236 // Don't notify; this node is still being created. 9237 comment->SetText(aData, false); 9238 return comment.forget(); 9239 } 9240 9241 already_AddRefed<CDATASection> Document::CreateCDATASection( 9242 const nsAString& aData, ErrorResult& rv) { 9243 if (IsHTMLDocument()) { 9244 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 9245 return nullptr; 9246 } 9247 9248 if (FindInReadable(u"]]>"_ns, aData)) { 9249 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR); 9250 return nullptr; 9251 } 9252 9253 RefPtr<CDATASection> cdata = 9254 new (mNodeInfoManager) CDATASection(mNodeInfoManager); 9255 9256 // Don't notify; this node is still being created. 9257 cdata->SetText(aData, false); 9258 9259 return cdata.forget(); 9260 } 9261 9262 already_AddRefed<ProcessingInstruction> Document::CreateProcessingInstruction( 9263 const nsAString& aTarget, const nsAString& aData, ErrorResult& rv) const { 9264 nsresult res = nsContentUtils::CheckQName(aTarget, false); 9265 if (NS_FAILED(res)) { 9266 rv.Throw(res); 9267 return nullptr; 9268 } 9269 9270 if (FindInReadable(u"?>"_ns, aData)) { 9271 rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR); 9272 return nullptr; 9273 } 9274 9275 RefPtr<ProcessingInstruction> pi = 9276 NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData); 9277 9278 return pi.forget(); 9279 } 9280 9281 already_AddRefed<Attr> Document::CreateAttribute(const nsAString& aName, 9282 ErrorResult& rv) { 9283 if (!mNodeInfoManager) { 9284 rv.Throw(NS_ERROR_NOT_INITIALIZED); 9285 return nullptr; 9286 } 9287 9288 nsresult res = nsContentUtils::CheckQName(aName, false); 9289 if (NS_FAILED(res)) { 9290 rv.Throw(res); 9291 return nullptr; 9292 } 9293 9294 nsAutoString name; 9295 if (IsHTMLDocument()) { 9296 nsContentUtils::ASCIIToLower(aName, name); 9297 } else { 9298 name = aName; 9299 } 9300 9301 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 9302 res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None, 9303 ATTRIBUTE_NODE, getter_AddRefs(nodeInfo)); 9304 if (NS_FAILED(res)) { 9305 rv.Throw(res); 9306 return nullptr; 9307 } 9308 9309 RefPtr<Attr> attribute = 9310 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns); 9311 return attribute.forget(); 9312 } 9313 9314 already_AddRefed<Attr> Document::CreateAttributeNS( 9315 const nsAString& aNamespaceURI, const nsAString& aQualifiedName, 9316 ErrorResult& rv) { 9317 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 9318 rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName, 9319 mNodeInfoManager, ATTRIBUTE_NODE, 9320 getter_AddRefs(nodeInfo)); 9321 if (rv.Failed()) { 9322 return nullptr; 9323 } 9324 9325 RefPtr<Attr> attribute = 9326 new (mNodeInfoManager) Attr(nullptr, nodeInfo.forget(), u""_ns); 9327 return attribute.forget(); 9328 } 9329 9330 void Document::ScheduleForPresAttrEvaluation(Element* aElement) { 9331 MOZ_ASSERT(aElement->IsInComposedDoc()); 9332 DebugOnly<bool> inserted = mLazyPresElements.EnsureInserted(aElement); 9333 MOZ_ASSERT(inserted); 9334 if (aElement->HasServoData()) { 9335 // TODO(emilio): RESTYLE_SELF is too strong, there should be no need to 9336 // re-selector-match, but right now this is needed to pick up the new mapped 9337 // attributes. We need something like RESTYLE_STYLE_ATTRIBUTE but for mapped 9338 // attributes. 9339 nsLayoutUtils::PostRestyleEvent(aElement, RestyleHint::RESTYLE_SELF, 9340 nsChangeHint(0)); 9341 } else if (auto* presContext = GetPresContext()) { 9342 presContext->RestyleManager()->IncrementUndisplayedRestyleGeneration(); 9343 } 9344 } 9345 9346 void Document::UnscheduleForPresAttrEvaluation(Element* aElement) { 9347 mLazyPresElements.Remove(aElement); 9348 } 9349 9350 void Document::DoResolveScheduledPresAttrs() { 9351 MOZ_ASSERT(!mLazyPresElements.IsEmpty()); 9352 for (Element* el : mLazyPresElements) { 9353 MOZ_ASSERT(el->IsInComposedDoc(), 9354 "Un-schedule when removing from the document"); 9355 MOZ_ASSERT(el->IsPendingMappedAttributeEvaluation()); 9356 if (auto* svg = SVGElement::FromNode(el)) { 9357 // SVG does its own (very similar) thing, for now at least. 9358 svg->UpdateMappedDeclarationBlock(); 9359 } else { 9360 MappedDeclarationsBuilder builder(*el, *this, 9361 el->GetMappedAttributeStyle()); 9362 auto function = el->GetAttributeMappingFunction(); 9363 function(builder); 9364 el->SetMappedDeclarationBlock(builder.TakeDeclarationBlock()); 9365 } 9366 MOZ_ASSERT(!el->IsPendingMappedAttributeEvaluation()); 9367 } 9368 mLazyPresElements.Clear(); 9369 } 9370 9371 already_AddRefed<nsSimpleContentList> Document::BlockedNodesByClassifier() 9372 const { 9373 RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr); 9374 9375 for (const nsWeakPtr& weakNode : mBlockedNodesByClassifier) { 9376 if (nsCOMPtr<nsIContent> node = do_QueryReferent(weakNode)) { 9377 // Consider only nodes to which we have managed to get strong references. 9378 // Coping with nullptrs since it's expected for nodes to disappear when 9379 // nobody else is referring to them. 9380 list->AppendElement(node); 9381 } 9382 } 9383 9384 return list.forget(); 9385 } 9386 9387 void Document::GetSelectedStyleSheetSet(nsAString& aSheetSet) { 9388 aSheetSet.Truncate(); 9389 9390 // Look through our sheets, find the selected set title 9391 size_t count = SheetCount(); 9392 nsAutoString title; 9393 for (size_t index = 0; index < count; index++) { 9394 StyleSheet* sheet = SheetAt(index); 9395 NS_ASSERTION(sheet, "Null sheet in sheet list!"); 9396 9397 if (sheet->Disabled()) { 9398 // Disabled sheets don't affect the currently selected set 9399 continue; 9400 } 9401 9402 sheet->GetTitle(title); 9403 9404 if (aSheetSet.IsEmpty()) { 9405 aSheetSet = title; 9406 } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) { 9407 // Sheets from multiple sets enabled; return null string, per spec. 9408 SetDOMStringToNull(aSheetSet); 9409 return; 9410 } 9411 } 9412 } 9413 9414 void Document::SetSelectedStyleSheetSet(const nsAString& aSheetSet) { 9415 if (DOMStringIsNull(aSheetSet)) { 9416 return; 9417 } 9418 9419 // Must update mLastStyleSheetSet before doing anything else with stylesheets 9420 // or CSSLoaders. 9421 mLastStyleSheetSet = aSheetSet; 9422 EnableStyleSheetsForSetInternal(aSheetSet, true); 9423 } 9424 9425 void Document::SetPreferredStyleSheetSet(const nsAString& aSheetSet) { 9426 mPreferredStyleSheetSet = aSheetSet; 9427 // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per 9428 // spec. 9429 if (DOMStringIsNull(mLastStyleSheetSet)) { 9430 // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet, 9431 // per spec. The idea here is that we're changing our preferred set and 9432 // that shouldn't change the value of lastStyleSheetSet. Also, we're 9433 // using the Internal version so we can update the CSSLoader and not have 9434 // to worry about null strings. 9435 EnableStyleSheetsForSetInternal(aSheetSet, true); 9436 } 9437 } 9438 9439 DOMStringList* Document::StyleSheetSets() { 9440 if (!mStyleSheetSetList) { 9441 mStyleSheetSetList = new DOMStyleSheetSetList(this); 9442 } 9443 return mStyleSheetSetList; 9444 } 9445 9446 void Document::EnableStyleSheetsForSet(const nsAString& aSheetSet) { 9447 // Per spec, passing in null is a no-op. 9448 if (!DOMStringIsNull(aSheetSet)) { 9449 // Note: must make sure to not change the CSSLoader's preferred sheet -- 9450 // that value should be equal to either our lastStyleSheetSet (if that's 9451 // non-null) or to our preferredStyleSheetSet. And this method doesn't 9452 // change either of those. 9453 EnableStyleSheetsForSetInternal(aSheetSet, false); 9454 } 9455 } 9456 9457 void Document::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet, 9458 bool aUpdateCSSLoader) { 9459 size_t count = SheetCount(); 9460 nsAutoString title; 9461 for (size_t index = 0; index < count; index++) { 9462 StyleSheet* sheet = SheetAt(index); 9463 NS_ASSERTION(sheet, "Null sheet in sheet list!"); 9464 9465 sheet->GetTitle(title); 9466 if (!title.IsEmpty()) { 9467 sheet->SetEnabled(title.Equals(aSheetSet)); 9468 } 9469 } 9470 if (aUpdateCSSLoader) { 9471 EnsureCSSLoader().DocumentStyleSheetSetChanged(); 9472 } 9473 if (EnsureStyleSet().StyleSheetsHaveChanged()) { 9474 ApplicableStylesChanged(); 9475 } 9476 } 9477 9478 void Document::GetCharacterSet(nsAString& aCharacterSet) const { 9479 nsAutoCString charset; 9480 GetDocumentCharacterSet()->Name(charset); 9481 CopyASCIItoUTF16(charset, aCharacterSet); 9482 } 9483 9484 already_AddRefed<nsINode> Document::ImportNode(nsINode& aNode, bool aDeep, 9485 ErrorResult& rv) const { 9486 nsINode* imported = &aNode; 9487 9488 switch (imported->NodeType()) { 9489 case DOCUMENT_NODE: { 9490 break; 9491 } 9492 case DOCUMENT_FRAGMENT_NODE: 9493 case ATTRIBUTE_NODE: 9494 case ELEMENT_NODE: 9495 case PROCESSING_INSTRUCTION_NODE: 9496 case TEXT_NODE: 9497 case CDATA_SECTION_NODE: 9498 case COMMENT_NODE: 9499 case DOCUMENT_TYPE_NODE: { 9500 return imported->Clone(aDeep, mNodeInfoManager, rv); 9501 } 9502 default: { 9503 NS_WARNING("Don't know how to clone this nodetype for importNode."); 9504 } 9505 } 9506 9507 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 9508 return nullptr; 9509 } 9510 9511 already_AddRefed<nsRange> Document::CreateRange(ErrorResult& rv) { 9512 return nsRange::Create(this, 0, this, 0, rv); 9513 } 9514 9515 already_AddRefed<NodeIterator> Document::CreateNodeIterator( 9516 nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter, 9517 ErrorResult& rv) const { 9518 RefPtr<NodeIterator> iterator = 9519 new NodeIterator(&aRoot, aWhatToShow, aFilter); 9520 return iterator.forget(); 9521 } 9522 9523 already_AddRefed<TreeWalker> Document::CreateTreeWalker(nsINode& aRoot, 9524 uint32_t aWhatToShow, 9525 NodeFilter* aFilter, 9526 ErrorResult& rv) const { 9527 RefPtr<TreeWalker> walker = new TreeWalker(&aRoot, aWhatToShow, aFilter); 9528 return walker.forget(); 9529 } 9530 9531 already_AddRefed<Location> Document::GetLocation() const { 9532 nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject); 9533 9534 if (!w) { 9535 return nullptr; 9536 } 9537 9538 return do_AddRef(w->Location()); 9539 } 9540 9541 already_AddRefed<nsIURI> Document::GetDomainURI() { 9542 nsIPrincipal* principal = NodePrincipal(); 9543 9544 nsCOMPtr<nsIURI> uri; 9545 principal->GetDomain(getter_AddRefs(uri)); 9546 if (uri) { 9547 return uri.forget(); 9548 } 9549 auto* basePrin = BasePrincipal::Cast(principal); 9550 basePrin->GetURI(getter_AddRefs(uri)); 9551 return uri.forget(); 9552 } 9553 9554 void Document::GetDomain(nsAString& aDomain) { 9555 nsCOMPtr<nsIURI> uri = GetDomainURI(); 9556 9557 if (!uri) { 9558 aDomain.Truncate(); 9559 return; 9560 } 9561 9562 nsAutoCString hostName; 9563 nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName); 9564 if (NS_SUCCEEDED(rv)) { 9565 CopyUTF8toUTF16(hostName, aDomain); 9566 } else { 9567 // If we can't get the host from the URI (e.g. about:, javascript:, 9568 // etc), just return an empty string. 9569 aDomain.Truncate(); 9570 } 9571 } 9572 9573 void Document::SetDomain(const nsAString& aDomain, ErrorResult& rv) { 9574 if (!GetBrowsingContext()) { 9575 // If our browsing context is null; disallow setting domain 9576 rv.Throw(NS_ERROR_DOM_SECURITY_ERR); 9577 return; 9578 } 9579 9580 if (mSandboxFlags & SANDBOXED_DOMAIN) { 9581 // We're sandboxed; disallow setting domain 9582 rv.Throw(NS_ERROR_DOM_SECURITY_ERR); 9583 return; 9584 } 9585 9586 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"document-domain"_ns)) { 9587 rv.Throw(NS_ERROR_DOM_SECURITY_ERR); 9588 return; 9589 } 9590 9591 if (aDomain.IsEmpty()) { 9592 rv.Throw(NS_ERROR_DOM_SECURITY_ERR); 9593 return; 9594 } 9595 9596 nsCOMPtr<nsIURI> uri = GetDomainURI(); 9597 if (!uri) { 9598 rv.Throw(NS_ERROR_FAILURE); 9599 return; 9600 } 9601 9602 // Check new domain - must be a superdomain of the current host 9603 // For example, a page from foo.bar.com may set domain to bar.com, 9604 // but not to ar.com, baz.com, or fi.foo.bar.com. 9605 9606 nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri); 9607 if (!newURI) { 9608 // Error: illegal domain 9609 rv.Throw(NS_ERROR_DOM_SECURITY_ERR); 9610 return; 9611 } 9612 9613 if (!GetDocGroup() || GetDocGroup()->IsOriginKeyed()) { 9614 WarnOnceAbout(Document::eDocumentSetDomainIgnored); 9615 return; 9616 } 9617 9618 MOZ_ALWAYS_SUCCEEDS(NodePrincipal()->SetDomain(newURI)); 9619 MOZ_ALWAYS_SUCCEEDS(PartitionedPrincipal()->SetDomain(newURI)); 9620 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) { 9621 wgc->SendSetDocumentDomain(WrapNotNull(newURI)); 9622 } 9623 } 9624 9625 already_AddRefed<nsIURI> Document::CreateInheritingURIForHost( 9626 const nsACString& aHostString) { 9627 if (aHostString.IsEmpty()) { 9628 return nullptr; 9629 } 9630 9631 // Create new URI 9632 nsCOMPtr<nsIURI> uri = GetDomainURI(); 9633 if (!uri) { 9634 return nullptr; 9635 } 9636 9637 nsresult rv; 9638 rv = NS_MutateURI(uri) 9639 .SetUserPass(""_ns) 9640 .SetPort(-1) // we want to reset the port number if needed. 9641 .SetHostPort(aHostString) 9642 .Finalize(uri); 9643 if (NS_FAILED(rv)) { 9644 return nullptr; 9645 } 9646 9647 return uri.forget(); 9648 } 9649 9650 already_AddRefed<nsIURI> Document::RegistrableDomainSuffixOfInternal( 9651 const nsAString& aNewDomain, nsIURI* aOrigHost) { 9652 if (NS_WARN_IF(!aOrigHost)) { 9653 return nullptr; 9654 } 9655 9656 nsCOMPtr<nsIURI> newURI = 9657 CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain)); 9658 if (!newURI) { 9659 // Error: failed to parse input domain 9660 return nullptr; 9661 } 9662 9663 if (!IsValidDomain(aOrigHost, newURI)) { 9664 // Error: illegal domain 9665 return nullptr; 9666 } 9667 9668 nsAutoCString domain; 9669 if (NS_FAILED(newURI->GetAsciiHost(domain))) { 9670 return nullptr; 9671 } 9672 9673 return CreateInheritingURIForHost(domain); 9674 } 9675 9676 /* static */ 9677 bool Document::IsValidDomain(nsIURI* aOrigHost, nsIURI* aNewURI) { 9678 // Check new domain - must be a superdomain of the current host 9679 // For example, a page from foo.bar.com may set domain to bar.com, 9680 // but not to ar.com, baz.com, or fi.foo.bar.com. 9681 nsAutoCString current; 9682 nsAutoCString domain; 9683 if (NS_FAILED(aOrigHost->GetAsciiHost(current))) { 9684 current.Truncate(); 9685 } 9686 if (NS_FAILED(aNewURI->GetAsciiHost(domain))) { 9687 domain.Truncate(); 9688 } 9689 9690 bool ok = current.Equals(domain); 9691 if (current.Length() > domain.Length() && StringEndsWith(current, domain) && 9692 current.CharAt(current.Length() - domain.Length() - 1) == '.') { 9693 // We're golden if the new domain is the current page's base domain or a 9694 // subdomain of it. 9695 nsCOMPtr<nsIEffectiveTLDService> tldService = 9696 mozilla::components::EffectiveTLD::Service(); 9697 if (!tldService) { 9698 return false; 9699 } 9700 9701 nsAutoCString currentBaseDomain; 9702 ok = NS_SUCCEEDED( 9703 tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain)); 9704 NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) == 9705 (domain.Length() >= currentBaseDomain.Length()), 9706 "uh-oh! slight optimization wasn't valid somehow!"); 9707 ok = ok && domain.Length() >= currentBaseDomain.Length(); 9708 } 9709 9710 return ok; 9711 } 9712 9713 Element* Document::GetHtmlElement() const { 9714 Element* rootElement = GetRootElement(); 9715 if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html)) { 9716 return rootElement; 9717 } 9718 return nullptr; 9719 } 9720 9721 Element* Document::GetHtmlChildElement( 9722 nsAtom* aTag, const nsIContent* aContentToIgnore) const { 9723 Element* html = GetHtmlElement(); 9724 if (!html) { 9725 return nullptr; 9726 } 9727 9728 // Look for the element with aTag inside html. This needs to run 9729 // forwards to find the first such element. 9730 for (nsIContent* child = html->GetFirstChild(); child; 9731 child = child->GetNextSibling()) { 9732 if (child->IsHTMLElement(aTag) && MOZ_LIKELY(child != aContentToIgnore)) { 9733 return child->AsElement(); 9734 } 9735 } 9736 return nullptr; 9737 } 9738 9739 nsGenericHTMLElement* Document::GetBody() const { 9740 Element* html = GetHtmlElement(); 9741 if (!html) { 9742 return nullptr; 9743 } 9744 9745 for (nsIContent* child = html->GetFirstChild(); child; 9746 child = child->GetNextSibling()) { 9747 if (child->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { 9748 return static_cast<nsGenericHTMLElement*>(child); 9749 } 9750 } 9751 9752 return nullptr; 9753 } 9754 9755 void Document::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv) { 9756 nsCOMPtr<Element> root = GetRootElement(); 9757 9758 // The body element must be either a body tag or a frameset tag. And we must 9759 // have a root element to be able to add kids to it. 9760 if (!newBody || 9761 !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { 9762 rv.ThrowHierarchyRequestError( 9763 "The new body must be either a body tag or frameset tag."); 9764 return; 9765 } 9766 9767 if (!root) { 9768 rv.ThrowHierarchyRequestError("No root element."); 9769 return; 9770 } 9771 9772 // Use DOM methods so that we pass through the appropriate security checks. 9773 nsCOMPtr<Element> currentBody = GetBody(); 9774 if (currentBody) { 9775 root->ReplaceChild(*newBody, *currentBody, rv); 9776 } else { 9777 root->AppendChild(*newBody, rv); 9778 } 9779 } 9780 9781 HTMLSharedElement* Document::GetHead() const { 9782 return static_cast<HTMLSharedElement*>(GetHeadElement()); 9783 } 9784 9785 Element* Document::GetTitleElement() { 9786 // mMayHaveTitleElement will have been set to true if any HTML or SVG 9787 // <title> element has been bound to this document. So if it's false, 9788 // we know there is nothing to do here. This avoids us having to search 9789 // the whole DOM if someone calls document.title on a large document 9790 // without a title. 9791 if (!mMayHaveTitleElement) { 9792 return nullptr; 9793 } 9794 9795 if (Element* root = GetSVGRootElement()) { 9796 // In SVG, the document's title must be a child 9797 for (nsIContent* child = root->GetFirstChild(); child; 9798 child = child->GetNextSibling()) { 9799 if (child->IsSVGElement(nsGkAtoms::title)) { 9800 return child->AsElement(); 9801 } 9802 } 9803 return nullptr; 9804 } 9805 9806 // We check the HTML namespace even for non-HTML documents, except SVG. This 9807 // matches the spec and the behavior of all tested browsers. 9808 for (nsINode* node = GetFirstChild(); node; node = node->GetNextNode(this)) { 9809 if (node->IsHTMLElement(nsGkAtoms::title)) { 9810 return node->AsElement(); 9811 } 9812 } 9813 return nullptr; 9814 } 9815 9816 void Document::GetTitle(nsAString& aTitle) { 9817 aTitle.Truncate(); 9818 9819 Element* rootElement = GetRootElement(); 9820 if (!rootElement) { 9821 return; 9822 } 9823 9824 if (rootElement->IsXULElement()) { 9825 rootElement->GetAttr(nsGkAtoms::title, aTitle); 9826 } else if (Element* title = GetTitleElement()) { 9827 nsContentUtils::GetNodeTextContent(title, false, aTitle); 9828 } else { 9829 return; 9830 } 9831 9832 aTitle.CompressWhitespace(); 9833 } 9834 9835 void Document::SetTitle(const nsAString& aTitle, ErrorResult& aRv) { 9836 Element* rootElement = GetRootElement(); 9837 if (!rootElement) { 9838 return; 9839 } 9840 9841 if (rootElement->IsXULElement()) { 9842 aRv = 9843 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true); 9844 return; 9845 } 9846 9847 Maybe<mozAutoDocUpdate> updateBatch; 9848 nsCOMPtr<Element> title = GetTitleElement(); 9849 if (rootElement->IsSVGElement(nsGkAtoms::svg)) { 9850 if (!title) { 9851 // Batch updates so that mutation events don't change "the title 9852 // element" under us 9853 updateBatch.emplace(this, true); 9854 RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo( 9855 nsGkAtoms::title, nullptr, kNameSpaceID_SVG, ELEMENT_NODE); 9856 NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(), 9857 NOT_FROM_PARSER); 9858 if (!title) { 9859 return; 9860 } 9861 rootElement->InsertChildBefore(title, rootElement->GetFirstChild(), true, 9862 IgnoreErrors()); 9863 } 9864 } else if (rootElement->IsHTMLElement()) { 9865 if (!title) { 9866 // Batch updates so that mutation events don't change "the title 9867 // element" under us 9868 updateBatch.emplace(this, true); 9869 Element* head = GetHeadElement(); 9870 if (!head) { 9871 return; 9872 } 9873 9874 RefPtr<mozilla::dom::NodeInfo> titleInfo; 9875 titleInfo = mNodeInfoManager->GetNodeInfo( 9876 nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE); 9877 title = NS_NewHTMLTitleElement(titleInfo.forget()); 9878 if (!title) { 9879 return; 9880 } 9881 9882 head->AppendChildTo(title, true, IgnoreErrors()); 9883 } 9884 } else { 9885 return; 9886 } 9887 9888 aRv = nsContentUtils::SetNodeTextContent(title, aTitle, false); 9889 } 9890 9891 class Document::TitleChangeEvent final : public Runnable { 9892 public: 9893 explicit TitleChangeEvent(Document* aDoc) 9894 : Runnable("Document::TitleChangeEvent"), 9895 mDoc(aDoc), 9896 mBlockOnload(aDoc->IsInChromeDocShell()) { 9897 if (mBlockOnload) { 9898 mDoc->BlockOnload(); 9899 } 9900 } 9901 9902 NS_IMETHOD Run() final { 9903 if (!mDoc) { 9904 return NS_OK; 9905 } 9906 const RefPtr<Document> doc = mDoc; 9907 const bool blockOnload = mBlockOnload; 9908 mDoc = nullptr; 9909 doc->DoNotifyPossibleTitleChange(); 9910 if (blockOnload) { 9911 doc->UnblockOnload(/* aFireSync = */ true); 9912 } 9913 return NS_OK; 9914 } 9915 9916 void Revoke() { 9917 if (mDoc) { 9918 if (mBlockOnload) { 9919 mDoc->UnblockOnload(/* aFireSync = */ false); 9920 } 9921 mDoc = nullptr; 9922 } 9923 } 9924 9925 private: 9926 // Weak, caller is responsible for calling Revoke() when needed. 9927 Document* mDoc; 9928 // title changes should block the load event on chrome docshells, so that the 9929 // window title is consistently set by the time the top window is displayed. 9930 // Otherwise, some window manager integrations don't work properly, 9931 // see bug 1874766. 9932 const bool mBlockOnload = false; 9933 }; 9934 9935 void Document::NotifyPossibleTitleChange(bool aBoundTitleElement) { 9936 NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement, 9937 "Setting a title while unlinking or destroying the element?"); 9938 if (mInUnlinkOrDeletion) { 9939 return; 9940 } 9941 9942 if (aBoundTitleElement) { 9943 mMayHaveTitleElement = true; 9944 } 9945 9946 if (mPendingTitleChangeEvent.IsPending()) { 9947 return; 9948 } 9949 9950 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 9951 RefPtr<TitleChangeEvent> event = new TitleChangeEvent(this); 9952 if (NS_WARN_IF(NS_FAILED(Dispatch(do_AddRef(event))))) { 9953 event->Revoke(); 9954 return; 9955 } 9956 mPendingTitleChangeEvent = std::move(event); 9957 } 9958 9959 void Document::DoNotifyPossibleTitleChange() { 9960 if (!mPendingTitleChangeEvent.IsPending()) { 9961 return; 9962 } 9963 // Make sure the pending runnable method is cleared. 9964 mPendingTitleChangeEvent.Revoke(); 9965 mHaveFiredTitleChange = true; 9966 9967 nsAutoString title; 9968 GetTitle(title); 9969 9970 if (RefPtr<PresShell> presShell = GetPresShell()) { 9971 nsCOMPtr<nsISupports> container = 9972 presShell->GetPresContext()->GetContainerWeak(); 9973 if (container) { 9974 if (nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container)) { 9975 docShellWin->SetTitle(title); 9976 } 9977 } 9978 } 9979 9980 if (WindowGlobalChild* child = GetWindowGlobalChild()) { 9981 child->SendUpdateDocumentTitle(title); 9982 } 9983 9984 // Fire a DOM event for the title change. 9985 nsContentUtils::DispatchChromeEvent(this, this, u"DOMTitleChanged"_ns, 9986 CanBubble::eYes, Cancelable::eYes); 9987 9988 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 9989 if (obs) { 9990 obs->NotifyObservers(ToSupports(this), "document-title-changed", nullptr); 9991 } 9992 } 9993 9994 already_AddRefed<MediaQueryList> Document::MatchMedia( 9995 const nsACString& aMediaQueryList, CallerType aCallerType) { 9996 RefPtr<MediaQueryList> result = 9997 new MediaQueryList(this, aMediaQueryList, aCallerType); 9998 9999 mDOMMediaQueryLists.insertBack(result); 10000 10001 return result.forget(); 10002 } 10003 10004 void Document::SetMayStartLayout(bool aMayStartLayout) { 10005 mMayStartLayout = aMayStartLayout; 10006 if (MayStartLayout()) { 10007 // Before starting layout, check whether we're a toplevel chrome 10008 // window. If we are, setup some state so that we don't have to restyle 10009 // the whole tree after StartLayout. 10010 if (nsCOMPtr<nsIAppWindow> win = GetAppWindowIfToplevelChrome()) { 10011 // We're the chrome document! 10012 win->BeforeStartLayout(); 10013 } 10014 ReadyState state = GetReadyStateEnum(); 10015 if (state >= READYSTATE_INTERACTIVE) { 10016 // DOMContentLoaded has fired already. 10017 MaybeResolveReadyForIdle(); 10018 } 10019 } 10020 10021 MaybeEditingStateChanged(); 10022 } 10023 10024 nsresult Document::InitializeFrameLoader(nsFrameLoader* aLoader) { 10025 mInitializableFrameLoaders.RemoveElement(aLoader); 10026 // Don't even try to initialize. 10027 if (mInDestructor) { 10028 NS_WARNING( 10029 "Trying to initialize a frame loader while" 10030 "document is being deleted"); 10031 return NS_ERROR_FAILURE; 10032 } 10033 10034 mInitializableFrameLoaders.AppendElement(aLoader); 10035 if (!mFrameLoaderRunner) { 10036 mFrameLoaderRunner = 10037 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this, 10038 &Document::MaybeInitializeFinalizeFrameLoaders); 10039 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY); 10040 nsContentUtils::AddScriptRunner(mFrameLoaderRunner); 10041 } 10042 return NS_OK; 10043 } 10044 10045 nsresult Document::FinalizeFrameLoader(nsFrameLoader* aLoader, 10046 nsIRunnable* aFinalizer) { 10047 mInitializableFrameLoaders.RemoveElement(aLoader); 10048 if (mInDestructor) { 10049 return NS_ERROR_FAILURE; 10050 } 10051 10052 LogRunnable::LogDispatch(aFinalizer); 10053 mFrameLoaderFinalizers.AppendElement(aFinalizer); 10054 if (!mFrameLoaderRunner) { 10055 mFrameLoaderRunner = 10056 NewRunnableMethod("Document::MaybeInitializeFinalizeFrameLoaders", this, 10057 &Document::MaybeInitializeFinalizeFrameLoaders); 10058 NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY); 10059 nsContentUtils::AddScriptRunner(mFrameLoaderRunner); 10060 } 10061 return NS_OK; 10062 } 10063 10064 void Document::MaybeInitializeFinalizeFrameLoaders() { 10065 if (mDelayFrameLoaderInitialization) { 10066 // This method will be recalled when !mDelayFrameLoaderInitialization. 10067 mFrameLoaderRunner = nullptr; 10068 return; 10069 } 10070 10071 // We're not in an update, but it is not safe to run scripts, so 10072 // postpone frameloader initialization and finalization. 10073 if (!nsContentUtils::IsSafeToRunScript()) { 10074 if (!mInDestructor && !mFrameLoaderRunner && 10075 (mInitializableFrameLoaders.Length() || 10076 mFrameLoaderFinalizers.Length())) { 10077 mFrameLoaderRunner = NewRunnableMethod( 10078 "Document::MaybeInitializeFinalizeFrameLoaders", this, 10079 &Document::MaybeInitializeFinalizeFrameLoaders); 10080 nsContentUtils::AddScriptRunner(mFrameLoaderRunner); 10081 } 10082 return; 10083 } 10084 mFrameLoaderRunner = nullptr; 10085 10086 // Don't use a temporary array for mInitializableFrameLoaders, because 10087 // loading a frame may cause some other frameloader to be removed from the 10088 // array. But be careful to keep the loader alive when starting the load! 10089 while (mInitializableFrameLoaders.Length()) { 10090 RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0]; 10091 mInitializableFrameLoaders.RemoveElementAt(0); 10092 NS_ASSERTION(loader, "null frameloader in the array?"); 10093 loader->ReallyStartLoading(); 10094 } 10095 10096 uint32_t length = mFrameLoaderFinalizers.Length(); 10097 if (length > 0) { 10098 nsTArray<nsCOMPtr<nsIRunnable>> finalizers = 10099 std::move(mFrameLoaderFinalizers); 10100 for (uint32_t i = 0; i < length; ++i) { 10101 LogRunnable::Run run(finalizers[i]); 10102 finalizers[i]->Run(); 10103 } 10104 } 10105 } 10106 10107 void Document::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) { 10108 uint32_t length = mInitializableFrameLoaders.Length(); 10109 for (uint32_t i = 0; i < length; ++i) { 10110 if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) { 10111 mInitializableFrameLoaders.RemoveElementAt(i); 10112 return; 10113 } 10114 } 10115 } 10116 10117 void Document::SetPrototypeDocument(nsXULPrototypeDocument* aPrototype) { 10118 mPrototypeDocument = aPrototype; 10119 mSynchronousDOMContentLoaded = true; 10120 } 10121 10122 nsIPermissionDelegateHandler* Document::PermDelegateHandler() { 10123 return GetPermissionDelegateHandler(); 10124 } 10125 10126 Document* Document::RequestExternalResource( 10127 nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode, 10128 ExternalResourceLoad** aPendingLoad) { 10129 MOZ_ASSERT(aURI, "Must have a URI"); 10130 MOZ_ASSERT(aRequestingNode, "Must have a node"); 10131 MOZ_ASSERT(aReferrerInfo, "Must have a referrerInfo"); 10132 if (mDisplayDocument) { 10133 return mDisplayDocument->RequestExternalResource( 10134 aURI, aReferrerInfo, aRequestingNode, aPendingLoad); 10135 } 10136 10137 return mExternalResourceMap.RequestResource( 10138 aURI, aReferrerInfo, aRequestingNode, this, aPendingLoad); 10139 } 10140 10141 void Document::EnumerateExternalResources(SubDocEnumFunc aCallback) const { 10142 mExternalResourceMap.EnumerateResources(aCallback); 10143 } 10144 10145 SMILAnimationController* Document::GetAnimationController() { 10146 // We create the animation controller lazily because most documents won't want 10147 // one and only SVG documents and the like will call this 10148 if (mAnimationController) return mAnimationController; 10149 // Refuse to create an Animation Controller for data documents. 10150 if (mLoadedAsData) { 10151 return nullptr; 10152 } 10153 10154 mAnimationController = new SMILAnimationController(this); 10155 10156 // If there's a presContext then check the animation mode and pause if 10157 // necessary. 10158 nsPresContext* context = GetPresContext(); 10159 if (mAnimationController && context && 10160 context->ImageAnimationMode() == imgIContainer::kDontAnimMode) { 10161 mAnimationController->Pause(SMILTimeContainer::PAUSE_USERPREF); 10162 } 10163 10164 // If we're hidden (or being hidden), notify the newly-created animation 10165 // controller. (Skip this check for SVG-as-an-image documents, though, 10166 // because they don't get OnPageShow / OnPageHide calls). 10167 if (!mIsShowing && !mIsBeingUsedAsImage) { 10168 mAnimationController->OnPageHide(); 10169 } 10170 10171 return mAnimationController; 10172 } 10173 10174 ScrollTimelineAnimationTracker* 10175 Document::GetOrCreateScrollTimelineAnimationTracker() { 10176 if (!mScrollTimelineAnimationTracker) { 10177 mScrollTimelineAnimationTracker = new ScrollTimelineAnimationTracker(this); 10178 } 10179 10180 return mScrollTimelineAnimationTracker; 10181 } 10182 10183 /** 10184 * Retrieve the "direction" property of the document. 10185 * 10186 * @lina 01/09/2001 10187 */ 10188 void Document::GetDir(nsAString& aDirection) const { 10189 aDirection.Truncate(); 10190 Element* rootElement = GetHtmlElement(); 10191 if (rootElement) { 10192 static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection); 10193 } 10194 } 10195 10196 /** 10197 * Set the "direction" property of the document. 10198 * 10199 * @lina 01/09/2001 10200 */ 10201 void Document::SetDir(const nsAString& aDirection) { 10202 Element* rootElement = GetHtmlElement(); 10203 if (rootElement) { 10204 rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true); 10205 } 10206 } 10207 10208 nsIHTMLCollection* Document::Images() { 10209 if (!mImages) { 10210 mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img, 10211 nsGkAtoms::img); 10212 } 10213 return mImages; 10214 } 10215 10216 nsIHTMLCollection* Document::Embeds() { 10217 if (!mEmbeds) { 10218 mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed, 10219 nsGkAtoms::embed); 10220 } 10221 return mEmbeds; 10222 } 10223 10224 static bool MatchLinks(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom, 10225 void* aData) { 10226 return aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) && 10227 aElement->HasAttr(nsGkAtoms::href); 10228 } 10229 10230 nsIHTMLCollection* Document::Links() { 10231 if (!mLinks) { 10232 mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr); 10233 } 10234 return mLinks; 10235 } 10236 10237 nsIHTMLCollection* Document::Forms() { 10238 if (!mForms) { 10239 // Please keep this in sync with nsHTMLDocument::GetFormsAndFormControls. 10240 mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form, 10241 nsGkAtoms::form); 10242 } 10243 10244 return mForms; 10245 } 10246 10247 nsIHTMLCollection* Document::Scripts() { 10248 if (!mScripts) { 10249 mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script, 10250 nsGkAtoms::script); 10251 } 10252 return mScripts; 10253 } 10254 10255 nsIHTMLCollection* Document::Applets() { 10256 if (!mApplets) { 10257 mApplets = new nsEmptyContentList(this); 10258 } 10259 return mApplets; 10260 } 10261 10262 static bool MatchAnchors(Element* aElement, int32_t aNamespaceID, nsAtom* aAtom, 10263 void* aData) { 10264 return aElement->IsHTMLElement(nsGkAtoms::a) && 10265 aElement->HasAttr(nsGkAtoms::name); 10266 } 10267 10268 nsIHTMLCollection* Document::Anchors() { 10269 if (!mAnchors) { 10270 mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr); 10271 } 10272 return mAnchors; 10273 } 10274 10275 mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> Document::Open( 10276 const nsACString& aURL, const nsAString& aName, const nsAString& aFeatures, 10277 ErrorResult& rv) { 10278 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this), 10279 "XOW should have caught this!"); 10280 10281 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow(); 10282 if (!window) { 10283 rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); 10284 return nullptr; 10285 } 10286 nsCOMPtr<nsPIDOMWindowOuter> outer = 10287 nsPIDOMWindowOuter::GetFromCurrentInner(window); 10288 if (!outer) { 10289 rv.Throw(NS_ERROR_NOT_INITIALIZED); 10290 return nullptr; 10291 } 10292 RefPtr<nsGlobalWindowOuter> win = nsGlobalWindowOuter::Cast(outer); 10293 RefPtr<BrowsingContext> newBC; 10294 rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newBC)); 10295 if (!newBC) { 10296 return nullptr; 10297 } 10298 return WindowProxyHolder(std::move(newBC)); 10299 } 10300 10301 Document* Document::Open(const Optional<nsAString>& /* unused */, 10302 const Optional<nsAString>& /* unused */, 10303 ErrorResult& aError) { 10304 // Implements 10305 // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps> 10306 10307 MOZ_ASSERT(nsContentUtils::CanCallerAccess(this), 10308 "XOW should have caught this!"); 10309 10310 // Step 1 -- throw if we're an XML document. 10311 if (!IsHTMLDocument() || mDisableDocWrite) { 10312 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 10313 return nullptr; 10314 } 10315 10316 // Step 2 -- throw if dynamic markup insertion should throw. 10317 if (ShouldThrowOnDynamicMarkupInsertion()) { 10318 aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 10319 return nullptr; 10320 } 10321 10322 // Step 3 -- get the entry document, so we can use it for security checks. 10323 nsCOMPtr<Document> callerDoc = GetEntryDocument(); 10324 if (!callerDoc) { 10325 if (nsIGlobalObject* callerGlobal = GetEntryGlobal()) { 10326 if (callerGlobal->IsXPCSandbox()) { 10327 if (nsIPrincipal* principal = callerGlobal->PrincipalOrNull()) { 10328 if (principal->Equals(NodePrincipal())) { 10329 // In case we're being called from some JS sandbox scope, 10330 // pretend that the caller is the document itself. 10331 callerDoc = this; 10332 } 10333 } 10334 } 10335 } 10336 10337 if (!callerDoc) { 10338 // If we're called from C++ or in some other way without an originating 10339 // document we can't do a document.open w/o changing the principal of the 10340 // document to something like about:blank (as that's the only sane thing 10341 // to do when we don't know the origin of this call), and since we can't 10342 // change the principals of a document for security reasons we'll have to 10343 // refuse to go ahead with this call. 10344 10345 aError.Throw(NS_ERROR_DOM_SECURITY_ERR); 10346 return nullptr; 10347 } 10348 } 10349 10350 // Step 4 -- make sure we're same-origin (not just same origin-domain) with 10351 // the entry document. 10352 if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) { 10353 aError.Throw(NS_ERROR_DOM_SECURITY_ERR); 10354 return nullptr; 10355 } 10356 10357 // Step 5 -- if we have an active parser with a nonzero script nesting level, 10358 // just no-op. 10359 if ((mParser && mParser->HasNonzeroScriptNestingLevel()) || mParserAborted) { 10360 return this; 10361 } 10362 10363 // Step 6 -- check for open() during unload. Per spec, this is just a check 10364 // of the ignore-opens-during-unload counter, but our unload event code 10365 // doesn't affect that counter yet (unlike pagehide and beforeunload, which 10366 // do), so we check for unload directly. 10367 if (ShouldIgnoreOpens()) { 10368 return this; 10369 } 10370 10371 RefPtr<nsDocShell> shell(mDocumentContainer); 10372 if (shell) { 10373 bool inUnload; 10374 shell->GetIsInUnload(&inUnload); 10375 if (inUnload) { 10376 return this; 10377 } 10378 } 10379 10380 // At this point we know this is a valid-enough document.open() call 10381 // and not a no-op. Increment our use counter. 10382 SetUseCounter(eUseCounter_custom_DocumentOpen); 10383 10384 // XXX The spec has changed. There is a new step 7 and step 8 has changed 10385 // a bit. 10386 10387 // Step 8 -- stop existing navigation of our browsing context (and all other 10388 // loads it's doing) if we're the active document of our browsing context. 10389 // Note that we do not want to stop anything if there is no existing 10390 // navigation. 10391 if (shell && IsCurrentActiveDocument() && 10392 shell->GetIsAttemptingToNavigate()) { 10393 shell->Stop(nsIWebNavigation::STOP_NETWORK); 10394 10395 // The Stop call may have cancelled the onload blocker request or 10396 // prevented it from getting added, so we need to make sure it gets added 10397 // to the document again otherwise the document could have a non-zero 10398 // onload block count without the onload blocker request being in the 10399 // loadgroup. 10400 EnsureOnloadBlocker(); 10401 } 10402 10403 // Step 9 -- clear event listeners out of our DOM tree 10404 for (nsINode* node : ShadowIncludingTreeIterator(*this)) { 10405 if (EventListenerManager* elm = node->GetExistingListenerManager()) { 10406 elm->RemoveAllListeners(); 10407 } 10408 } 10409 10410 // Step 10 -- clear event listeners from our window, if we have one. 10411 // 10412 // Note that we explicitly want the inner window, and only if we're its 10413 // document. We want to do this (per spec) even when we're not the "active 10414 // document", so we can't go through GetWindow(), because it might forward to 10415 // the wrong inner. 10416 if (nsPIDOMWindowInner* win = GetInnerWindow()) { 10417 if (win->GetExtantDoc() == this) { 10418 if (EventListenerManager* elm = 10419 nsGlobalWindowInner::Cast(win)->GetExistingListenerManager()) { 10420 elm->RemoveAllListeners(); 10421 } 10422 } 10423 } 10424 10425 // If we have a parser that has a zero script nesting level, we need to 10426 // properly terminate it. We do that after we've removed all the event 10427 // listeners (so termination won't trigger event listeners if it does 10428 // something to the DOM), but before we remove all elements from the document 10429 // (so if termination does modify the DOM in some way we will just blow it 10430 // away immediately. See the similar code in WriteCommon that handles the 10431 // !IsInsertionPointDefined() case and should stay in sync with this code. 10432 if (mParser) { 10433 MOZ_ASSERT(!mParser->HasNonzeroScriptNestingLevel(), 10434 "Why didn't we take the early return?"); 10435 // Make sure we don't re-enter. 10436 IgnoreOpensDuringUnload ignoreOpenGuard(this); 10437 mParser->Terminate(); 10438 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out"); 10439 } 10440 10441 // Steps 11, 12, 13, 14 -- 10442 // remove all our DOM kids without notifying DevTools of the node removals. 10443 { 10444 // XXX I don't know we should keep hiding the node removals from DevTools. 10445 // If it's safe even if the user updates the DOM tree from Inspector or 10446 // Console, we can stop suppressing this. 10447 AutoSuppressNotifyingDevToolsOfNodeRemovals suppressNotifyingDevTools( 10448 *this); 10449 10450 // We want to ignore any recursive calls to Open() that happen while 10451 // disconnecting the node tree. The spec doesn't say to do this, but the 10452 // spec also doesn't envision unload events on subframes firing while we do 10453 // this, while all browsers fire them in practice. See 10454 // <https://github.com/whatwg/html/issues/4611>. 10455 IgnoreOpensDuringUnload ignoreOpenGuard(this); 10456 DisconnectNodeTree(); 10457 } 10458 10459 // Step 15 -- if we're the current document in our docshell, do the 10460 // equivalent of pushState() with the new URL we should have. 10461 if (shell && IsCurrentActiveDocument()) { 10462 nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI(); 10463 if (callerDoc != this) { 10464 nsCOMPtr<nsIURI> noFragmentURI; 10465 nsresult rv = NS_GetURIWithoutRef(newURI, getter_AddRefs(noFragmentURI)); 10466 if (NS_WARN_IF(NS_FAILED(rv))) { 10467 aError.Throw(rv); 10468 return nullptr; 10469 } 10470 newURI = std::move(noFragmentURI); 10471 } 10472 10473 // UpdateURLAndHistory might do various member-setting, so make sure we're 10474 // holding strong refs to all the refcounted args on the stack. We can 10475 // assume that our caller is holding on to "this" already. 10476 nsCOMPtr<nsIURI> currentURI = GetDocumentURI(); 10477 bool equalURIs; 10478 nsresult rv = currentURI->Equals(newURI, &equalURIs); 10479 if (NS_WARN_IF(NS_FAILED(rv))) { 10480 aError.Throw(rv); 10481 return nullptr; 10482 } 10483 nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer); 10484 rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, 10485 NavigationHistoryBehavior::Replace, 10486 currentURI, equalURIs); 10487 if (NS_WARN_IF(NS_FAILED(rv))) { 10488 aError.Throw(rv); 10489 return nullptr; 10490 } 10491 10492 // And use the security info of the caller document as well, since 10493 // it's the thing providing our data. 10494 mSecurityInfo = callerDoc->GetSecurityInfo(); 10495 10496 // Step 16 10497 if (IsInitialDocument()) { 10498 SetInitialStatus(Document::InitialStatus::IsInitialButExplicitlyOpened); 10499 } 10500 10501 // And let our docloader know that it will need to track our load event. 10502 nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded(); 10503 } 10504 10505 // Per spec nothing happens with our URI in other cases, though note 10506 // <https://github.com/whatwg/html/issues/4286>. 10507 10508 // Note that we don't need to do anything here with base URIs per spec. 10509 // That said, this might be assuming that we implement 10510 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url 10511 // correctly, which we don't right now for the about:blank case. 10512 10513 // Step 17, but note <https://github.com/whatwg/html/issues/4292>. 10514 mSkipLoadEventAfterClose = mLoadEventFiring; 10515 10516 // Preliminary to steps 18-21. Set our ready state to uninitialized before 10517 // we do anything else, so we can then proceed to later ready state levels. 10518 SetReadyStateInternal(READYSTATE_UNINITIALIZED, 10519 /* updateTimingInformation = */ false); 10520 // Reset a flag that affects readyState behavior. 10521 mSetCompleteAfterDOMContentLoaded = false; 10522 10523 // Step 18 -- set our compat mode to standards. 10524 SetCompatibilityMode(eCompatibility_FullStandards); 10525 10526 // Step 19 -- create a new parser associated with document. This also does 10527 // step 20 implicitly. 10528 mParserAborted = false; 10529 RefPtr<nsHtml5Parser> parser = nsHtml5Module::NewHtml5Parser(); 10530 mParser = parser; 10531 parser->Initialize(this, GetDocumentURI(), ToSupports(shell), nullptr); 10532 nsresult rv = parser->StartExecutor(); 10533 if (NS_WARN_IF(NS_FAILED(rv))) { 10534 aError.Throw(rv); 10535 return nullptr; 10536 } 10537 10538 // Clear out our form control state, because the state of controls 10539 // in the pre-open() document should not affect the state of 10540 // controls that are now going to be written. 10541 mLayoutHistoryState = nullptr; 10542 10543 if (shell) { 10544 // Prepare the docshell and the document viewer for the impending 10545 // out-of-band document.write() 10546 shell->PrepareForNewContentModel(); 10547 10548 nsCOMPtr<nsIDocumentViewer> viewer; 10549 shell->GetDocViewer(getter_AddRefs(viewer)); 10550 if (viewer) { 10551 viewer->LoadStart(this); 10552 } 10553 } 10554 10555 // Step 21. 10556 SetReadyStateInternal(Document::READYSTATE_LOADING, 10557 /* updateTimingInformation = */ false); 10558 10559 // Step 22. 10560 return this; 10561 } 10562 10563 void Document::Close(ErrorResult& rv) { 10564 if (!IsHTMLDocument()) { 10565 // No calling document.close() on XHTML! 10566 10567 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 10568 return; 10569 } 10570 10571 if (ShouldThrowOnDynamicMarkupInsertion()) { 10572 rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 10573 return; 10574 } 10575 10576 if (!mParser || !mParser->IsScriptCreated()) { 10577 return; 10578 } 10579 10580 ++mWriteLevel; 10581 rv = (static_cast<nsHtml5Parser*>(mParser.get())) 10582 ->Parse(u""_ns, nullptr, true); 10583 --mWriteLevel; 10584 } 10585 10586 void Document::WriteCommon(const Sequence<OwningTrustedHTMLOrString>& aText, 10587 bool aNewlineTerminate, 10588 nsIPrincipal* aSubjectPrincipal, 10589 mozilla::ErrorResult& rv) { 10590 bool isTrusted = true; 10591 auto getAsString = 10592 [&isTrusted](const OwningTrustedHTMLOrString& aTrustedHTMLOrString) { 10593 if (aTrustedHTMLOrString.IsString()) { 10594 isTrusted = false; 10595 return &aTrustedHTMLOrString.GetAsString(); 10596 } 10597 return &aTrustedHTMLOrString.GetAsTrustedHTML()->mData; 10598 }; 10599 10600 // Fast path the common case 10601 if (aText.Length() == 1) { 10602 WriteCommon(*getAsString(aText[0]), aNewlineTerminate, 10603 aText[0].IsTrustedHTML(), aSubjectPrincipal, rv); 10604 } else { 10605 // XXXbz it would be nice if we could pass all the strings to the parser 10606 // without having to do all this copying and then ask it to start 10607 // parsing.... 10608 nsString text; 10609 for (size_t i = 0; i < aText.Length(); ++i) { 10610 text.Append(*getAsString(aText[i])); 10611 } 10612 WriteCommon(text, aNewlineTerminate, isTrusted, aSubjectPrincipal, rv); 10613 } 10614 } 10615 10616 void Document::WriteCommon(const nsAString& aText, bool aNewlineTerminate, 10617 bool aIsTrusted, nsIPrincipal* aSubjectPrincipal, 10618 ErrorResult& aRv) { 10619 #ifdef DEBUG 10620 { 10621 // Assert that we do not use or accidentally introduce doc.write() 10622 // in system privileged context or in any of our about: pages. 10623 nsCOMPtr<nsIPrincipal> principal = NodePrincipal(); 10624 bool isAboutOrPrivContext = principal->IsSystemPrincipal(); 10625 if (!isAboutOrPrivContext) { 10626 if (principal->SchemeIs("about")) { 10627 // about:blank inherits the security contetext and this assertion 10628 // is only meant for actual about: pages. 10629 nsAutoCString host; 10630 principal->GetHost(host); 10631 isAboutOrPrivContext = !host.EqualsLiteral("blank"); 10632 } 10633 } 10634 // Some automated tests use an empty string to kick off some parsing 10635 // mechansims, but they do not do any harm since they use an empty string. 10636 MOZ_ASSERT(!isAboutOrPrivContext || aText.IsEmpty(), 10637 "do not use doc.write in privileged context!"); 10638 } 10639 #endif 10640 10641 mTooDeepWriteRecursion = 10642 (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion); 10643 if (NS_WARN_IF(mTooDeepWriteRecursion)) { 10644 aRv.Throw(NS_ERROR_UNEXPECTED); 10645 return; 10646 } 10647 10648 Maybe<nsAutoString> compliantStringHolder; 10649 const nsAString* compliantString = &aText; 10650 if (!aIsTrusted) { 10651 constexpr nsLiteralString sinkWrite = u"Document write"_ns; 10652 constexpr nsLiteralString sinkWriteLn = u"Document writeln"_ns; 10653 compliantString = 10654 TrustedTypeUtils::GetTrustedTypesCompliantStringForTrustedHTML( 10655 aText, aNewlineTerminate ? sinkWriteLn : sinkWrite, 10656 kTrustedTypesOnlySinkGroup, *this, aSubjectPrincipal, 10657 compliantStringHolder, aRv); 10658 if (aRv.Failed()) { 10659 return; 10660 } 10661 } 10662 10663 if (!IsHTMLDocument() || mDisableDocWrite) { 10664 // No calling document.write*() on XHTML! 10665 10666 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 10667 return; 10668 } 10669 10670 if (ShouldThrowOnDynamicMarkupInsertion()) { 10671 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 10672 return; 10673 } 10674 10675 if (mParserAborted) { 10676 // Hixie says aborting the parser doesn't undefine the insertion point. 10677 // However, since we null out mParser in that case, we track the 10678 // theoretically defined insertion point using mParserAborted. 10679 return; 10680 } 10681 10682 // Implement Step 4.1 of: 10683 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps 10684 if (ShouldIgnoreOpens()) { 10685 return; 10686 } 10687 10688 void* key = GenerateParserKey(); 10689 if (mParser && !mParser->IsInsertionPointDefined()) { 10690 if (mIgnoreDestructiveWritesCounter) { 10691 // Instead of implying a call to document.open(), ignore the call. 10692 nsContentUtils::ReportToConsole( 10693 nsIScriptError::warningFlag, "DOM Events"_ns, this, 10694 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored"); 10695 return; 10696 } 10697 // The spec doesn't tell us to ignore opens from here, but we need to 10698 // ensure opens are ignored here. See similar code in Open() that handles 10699 // the case of an existing parser which is not currently running script and 10700 // should stay in sync with this code. 10701 IgnoreOpensDuringUnload ignoreOpenGuard(this); 10702 mParser->Terminate(); 10703 MOZ_RELEASE_ASSERT(!mParser, "mParser should have been null'd out"); 10704 } 10705 10706 if (!mParser) { 10707 if (mIgnoreDestructiveWritesCounter) { 10708 // Instead of implying a call to document.open(), ignore the call. 10709 nsContentUtils::ReportToConsole( 10710 nsIScriptError::warningFlag, "DOM Events"_ns, this, 10711 nsContentUtils::eDOM_PROPERTIES, "DocumentWriteIgnored"); 10712 return; 10713 } 10714 10715 Open({}, {}, aRv); 10716 10717 // If Open() fails, or if it didn't create a parser (as it won't 10718 // if the user chose to not discard the current document through 10719 // onbeforeunload), don't write anything. 10720 if (aRv.Failed() || !mParser) { 10721 return; 10722 } 10723 } 10724 10725 static constexpr auto new_line = u"\n"_ns; 10726 10727 ++mWriteLevel; 10728 10729 // This could be done with less code, but for performance reasons it 10730 // makes sense to have the code for two separate Parse() calls here 10731 // since the concatenation of strings costs more than we like. And 10732 // why pay that price when we don't need to? 10733 if (aNewlineTerminate) { 10734 aRv = (static_cast<nsHtml5Parser*>(mParser.get())) 10735 ->Parse(*compliantString + new_line, key, false); 10736 } else { 10737 aRv = (static_cast<nsHtml5Parser*>(mParser.get())) 10738 ->Parse(*compliantString, key, false); 10739 }; 10740 10741 --mWriteLevel; 10742 10743 mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion); 10744 } 10745 10746 void Document::Write(const Sequence<OwningTrustedHTMLOrString>& aText, 10747 nsIPrincipal* aSubjectPrincipal, ErrorResult& rv) { 10748 WriteCommon(aText, false, aSubjectPrincipal, rv); 10749 } 10750 10751 void Document::Writeln(const Sequence<OwningTrustedHTMLOrString>& aText, 10752 nsIPrincipal* aSubjectPrincipal, ErrorResult& rv) { 10753 WriteCommon(aText, true, aSubjectPrincipal, rv); 10754 } 10755 10756 void* Document::GenerateParserKey(void) { 10757 if (!mScriptLoader) { 10758 // If we don't have a script loader, then the parser probably isn't parsing 10759 // anything anyway, so just return null. 10760 return nullptr; 10761 } 10762 10763 // The script loader provides us with the currently executing script element, 10764 // which is guaranteed to be unique per script. 10765 nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript(); 10766 if (script && mParser && mParser->IsScriptCreated()) { 10767 nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser(); 10768 if (creatorParser != mParser) { 10769 // Make scripts that aren't inserted by the active parser of this document 10770 // participate in the context of the script that document.open()ed 10771 // this document. 10772 return nullptr; 10773 } 10774 } 10775 return script; 10776 } 10777 10778 /* static */ 10779 bool Document::MatchNameAttribute(Element* aElement, int32_t aNamespaceID, 10780 nsAtom* aAtom, void* aData) { 10781 MOZ_ASSERT(aElement, "Must have element to work with!"); 10782 10783 if (!aElement->HasName()) { 10784 return false; 10785 } 10786 10787 nsString* elementName = static_cast<nsString*>(aData); 10788 return aElement->GetNameSpaceID() == kNameSpaceID_XHTML && 10789 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, *elementName, 10790 eCaseMatters); 10791 } 10792 10793 /* static */ 10794 void* Document::UseExistingNameString(nsINode* aRootNode, 10795 const nsString* aName) { 10796 return const_cast<nsString*>(aName); 10797 } 10798 10799 nsresult Document::GetDocumentURI(nsString& aDocumentURI) const { 10800 if (mDocumentURI) { 10801 nsAutoCString uri; 10802 nsresult rv = mDocumentURI->GetSpec(uri); 10803 NS_ENSURE_SUCCESS(rv, rv); 10804 10805 CopyUTF8toUTF16(uri, aDocumentURI); 10806 } else { 10807 aDocumentURI.Truncate(); 10808 } 10809 10810 return NS_OK; 10811 } 10812 10813 // Alias of above 10814 nsresult Document::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); } 10815 10816 void Document::GetDocumentURIFromJS(nsString& aDocumentURI, 10817 CallerType aCallerType, 10818 ErrorResult& aRv) const { 10819 if (!mChromeXHRDocURI || aCallerType != CallerType::System) { 10820 aRv = GetDocumentURI(aDocumentURI); 10821 return; 10822 } 10823 10824 nsAutoCString uri; 10825 nsresult res = mChromeXHRDocURI->GetSpec(uri); 10826 if (NS_FAILED(res)) { 10827 aRv.Throw(res); 10828 return; 10829 } 10830 CopyUTF8toUTF16(uri, aDocumentURI); 10831 } 10832 10833 nsIURI* Document::GetDocumentURIObject() const { 10834 if (!mChromeXHRDocURI) { 10835 return GetDocumentURI(); 10836 } 10837 10838 return mChromeXHRDocURI; 10839 } 10840 10841 void Document::GetCompatMode(nsString& aCompatMode) const { 10842 NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks || 10843 mCompatMode == eCompatibility_AlmostStandards || 10844 mCompatMode == eCompatibility_FullStandards, 10845 "mCompatMode is neither quirks nor strict for this document"); 10846 10847 if (mCompatMode == eCompatibility_NavQuirks) { 10848 aCompatMode.AssignLiteral("BackCompat"); 10849 } else { 10850 aCompatMode.AssignLiteral("CSS1Compat"); 10851 } 10852 } 10853 10854 } // namespace dom 10855 } // namespace mozilla 10856 10857 void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode) { 10858 if (Element* element = Element::FromNode(aNode)) { 10859 if (const nsDOMAttributeMap* map = element->GetAttributeMap()) { 10860 while (true) { 10861 RefPtr<Attr> attr; 10862 { 10863 // Use an iterator to get an arbitrary attribute from the 10864 // cache. The iterator must be destroyed before any other 10865 // operations on mAttributeCache, to avoid hash table 10866 // assertions. 10867 auto iter = map->mAttributeCache.ConstIter(); 10868 if (iter.Done()) { 10869 break; 10870 } 10871 attr = iter.UserData(); 10872 } 10873 10874 BlastSubtreeToPieces(attr); 10875 10876 mozilla::DebugOnly<nsresult> rv = 10877 element->UnsetAttr(attr->NodeInfo()->NamespaceID(), 10878 attr->NodeInfo()->NameAtom(), false); 10879 10880 // XXX Should we abort here? 10881 NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!"); 10882 } 10883 } 10884 10885 if (mozilla::dom::ShadowRoot* shadow = element->GetShadowRoot()) { 10886 BlastSubtreeToPieces(shadow); 10887 element->UnattachShadow(); 10888 } 10889 } 10890 10891 while (aNode->HasChildren()) { 10892 nsIContent* node = aNode->GetFirstChild(); 10893 BlastSubtreeToPieces(node); 10894 aNode->RemoveChildNode(node, false); 10895 } 10896 } 10897 10898 namespace mozilla::dom { 10899 10900 nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv, 10901 bool aAcceptShadowRoot) { 10902 OwningNonNull<nsINode> adoptedNode = aAdoptedNode; 10903 if (adoptedNode->IsShadowRoot() && !aAcceptShadowRoot) { 10904 rv.ThrowHierarchyRequestError("The adopted node is a shadow root."); 10905 return nullptr; 10906 } 10907 10908 if (adoptedNode->GetParentNode()) { 10909 nsContentUtils::NotifyDevToolsOfNodeRemoval(*adoptedNode); 10910 } 10911 10912 nsAutoScriptBlocker scriptBlocker; 10913 10914 switch (adoptedNode->NodeType()) { 10915 case ATTRIBUTE_NODE: { 10916 // Remove from ownerElement. 10917 OwningNonNull<Attr> adoptedAttr = static_cast<Attr&>(*adoptedNode); 10918 10919 nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement(); 10920 if (rv.Failed()) { 10921 return nullptr; 10922 } 10923 10924 if (ownerElement) { 10925 OwningNonNull<Attr> newAttr = 10926 ownerElement->RemoveAttributeNode(*adoptedAttr, rv); 10927 if (rv.Failed()) { 10928 return nullptr; 10929 } 10930 } 10931 10932 break; 10933 } 10934 case DOCUMENT_FRAGMENT_NODE: 10935 case ELEMENT_NODE: 10936 case PROCESSING_INSTRUCTION_NODE: 10937 case TEXT_NODE: 10938 case CDATA_SECTION_NODE: 10939 case COMMENT_NODE: 10940 case DOCUMENT_TYPE_NODE: { 10941 // Don't allow adopting a node's anonymous subtree out from under it. 10942 if (adoptedNode->IsRootOfNativeAnonymousSubtree()) { 10943 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 10944 return nullptr; 10945 } 10946 10947 // We don't want to adopt an element into its own contentDocument or into 10948 // a descendant contentDocument, so we check if the frameElement of this 10949 // document or any of its parents is the adopted node or one of its 10950 // descendants. 10951 RefPtr<BrowsingContext> bc = GetBrowsingContext(); 10952 while (bc) { 10953 nsCOMPtr<nsINode> node = bc->GetEmbedderElement(); 10954 if (node && node->IsInclusiveDescendantOf(adoptedNode)) { 10955 rv.ThrowHierarchyRequestError( 10956 "Trying to adopt a node into its own contentDocument or a " 10957 "descendant contentDocument."); 10958 return nullptr; 10959 } 10960 10961 if (XRE_IsParentProcess()) { 10962 bc = bc->Canonical()->GetParentCrossChromeBoundary(); 10963 } else { 10964 bc = bc->GetParent(); 10965 } 10966 } 10967 10968 // Remove from parent. 10969 nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode(); 10970 if (parent) { 10971 parent->RemoveChildNode(adoptedNode->AsContent(), true); 10972 } else { 10973 MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc()); 10974 } 10975 10976 break; 10977 } 10978 case DOCUMENT_NODE: { 10979 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 10980 return nullptr; 10981 } 10982 default: { 10983 NS_WARNING("Don't know how to adopt this nodetype for adoptNode."); 10984 10985 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 10986 return nullptr; 10987 } 10988 } 10989 10990 nsCOMPtr<Document> oldDocument = adoptedNode->OwnerDoc(); 10991 bool sameDocument = oldDocument == this; 10992 10993 AutoJSContext cx; 10994 JS::Rooted<JSObject*> newScope(cx, nullptr); 10995 if (!sameDocument) { 10996 newScope = GetWrapper(); 10997 if (!newScope && GetScopeObject() && GetScopeObject()->HasJSGlobal()) { 10998 // Make sure cx is in a semi-sane compartment before we call WrapNative. 10999 // It's kind of irrelevant, given that we're passing aAllowWrapping = 11000 // false, and documents should always insist on being wrapped in an 11001 // canonical scope. But we try to pass something sane anyway. 11002 JSObject* globalObject = GetScopeObject()->GetGlobalJSObject(); 11003 JSAutoRealm ar(cx, globalObject); 11004 JS::Rooted<JS::Value> v(cx); 11005 rv = nsContentUtils::WrapNative(cx, ToSupports(this), this, &v, 11006 /* aAllowWrapping = */ false); 11007 if (rv.Failed()) return nullptr; 11008 newScope = &v.toObject(); 11009 } 11010 } 11011 11012 adoptedNode->Adopt(sameDocument ? nullptr : mNodeInfoManager, newScope, rv); 11013 if (rv.Failed()) { 11014 // Disconnect all nodes from their parents, since some have the old document 11015 // as their ownerDocument and some have this as their ownerDocument. 11016 nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode); 11017 return nullptr; 11018 } 11019 11020 MOZ_ASSERT(adoptedNode->OwnerDoc() == this, 11021 "Should still be in the document we just got adopted into"); 11022 11023 return adoptedNode; 11024 } 11025 11026 bool Document::UseWidthDeviceWidthFallbackViewport() const { return false; } 11027 11028 static Maybe<LayoutDeviceToScreenScale> ParseScaleString( 11029 const nsString& aScaleString) { 11030 // https://drafts.csswg.org/css-device-adapt/#min-scale-max-scale 11031 if (aScaleString.EqualsLiteral("device-width") || 11032 aScaleString.EqualsLiteral("device-height")) { 11033 return Some(LayoutDeviceToScreenScale(10.0f)); 11034 } else if (aScaleString.EqualsLiteral("yes")) { 11035 return Some(LayoutDeviceToScreenScale(1.0f)); 11036 } else if (aScaleString.EqualsLiteral("no")) { 11037 return Some(LayoutDeviceToScreenScale(ViewportMinScale())); 11038 } else if (aScaleString.IsEmpty()) { 11039 return Nothing(); 11040 } 11041 11042 nsresult scaleErrorCode; 11043 float scale = aScaleString.ToFloatAllowTrailingChars(&scaleErrorCode); 11044 if (NS_FAILED(scaleErrorCode)) { 11045 return Some(LayoutDeviceToScreenScale(ViewportMinScale())); 11046 } 11047 11048 if (scale < 0) { 11049 return Nothing(); 11050 } 11051 return Some(std::clamp(LayoutDeviceToScreenScale(scale), ViewportMinScale(), 11052 ViewportMaxScale())); 11053 } 11054 11055 void Document::ParseScalesInViewportMetaData( 11056 const ViewportMetaData& aViewportMetaData) { 11057 Maybe<LayoutDeviceToScreenScale> scale; 11058 11059 scale = ParseScaleString(aViewportMetaData.mInitialScale); 11060 mScaleFloat = scale.valueOr(LayoutDeviceToScreenScale(0.0f)); 11061 mValidScaleFloat = scale.isSome(); 11062 11063 scale = ParseScaleString(aViewportMetaData.mMaximumScale); 11064 // Chrome uses '5' for the fallback value of maximum-scale, we might 11065 // consider matching it in future. 11066 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_meta_element.cc?l=452&rcl=65ca4278b42d269ca738fc93ef7ae04a032afeb0 11067 mScaleMaxFloat = scale.valueOr(ViewportMaxScale()); 11068 mValidMaxScale = scale.isSome(); 11069 11070 scale = ParseScaleString(aViewportMetaData.mMinimumScale); 11071 mScaleMinFloat = scale.valueOr(ViewportMinScale()); 11072 mValidMinScale = scale.isSome(); 11073 11074 // Resolve min-zoom and max-zoom values. 11075 // https://drafts.csswg.org/css-device-adapt/#constraining-min-max-zoom 11076 if (mValidMaxScale && mValidMinScale) { 11077 mScaleMaxFloat = std::max(mScaleMinFloat, mScaleMaxFloat); 11078 } 11079 } 11080 11081 void Document::ParseWidthAndHeightInMetaViewport(const nsAString& aWidthString, 11082 const nsAString& aHeightString, 11083 bool aHasValidScale) { 11084 // The width and height properties 11085 // https://drafts.csswg.org/css-device-adapt/#width-and-height-properties 11086 // 11087 // The width and height viewport <META> properties are translated into width 11088 // and height descriptors, setting the min-width/min-height value to 11089 // extend-to-zoom and the max-width/max-height value to the length from the 11090 // viewport <META> property as follows: 11091 // 11092 // 1. Non-negative number values are translated to pixel lengths, clamped to 11093 // the range: [1px, 10000px] 11094 // 2. Negative number values are dropped 11095 // 3. device-width and device-height translate to 100vw and 100vh respectively 11096 // 4. Other keywords and unknown values are also dropped 11097 mMinWidth = nsViewportInfo::kAuto; 11098 mMaxWidth = nsViewportInfo::kAuto; 11099 if (!aWidthString.IsEmpty()) { 11100 mMinWidth = nsViewportInfo::kExtendToZoom; 11101 if (aWidthString.EqualsLiteral("device-width")) { 11102 mMaxWidth = nsViewportInfo::kDeviceSize; 11103 } else { 11104 nsresult widthErrorCode; 11105 mMaxWidth = aWidthString.ToInteger(&widthErrorCode); 11106 if (NS_FAILED(widthErrorCode)) { 11107 mMaxWidth = nsViewportInfo::kAuto; 11108 } else if (mMaxWidth >= 0.0f) { 11109 mMaxWidth = std::clamp(mMaxWidth, CSSCoord(1.0f), CSSCoord(10000.0f)); 11110 } else { 11111 mMaxWidth = nsViewportInfo::kAuto; 11112 } 11113 } 11114 } else if (aHasValidScale) { 11115 if (aHeightString.IsEmpty()) { 11116 mMinWidth = nsViewportInfo::kExtendToZoom; 11117 mMaxWidth = nsViewportInfo::kExtendToZoom; 11118 } 11119 } else if (aHeightString.IsEmpty() && UseWidthDeviceWidthFallbackViewport()) { 11120 mMinWidth = nsViewportInfo::kExtendToZoom; 11121 mMaxWidth = nsViewportInfo::kDeviceSize; 11122 } 11123 11124 mMinHeight = nsViewportInfo::kAuto; 11125 mMaxHeight = nsViewportInfo::kAuto; 11126 if (!aHeightString.IsEmpty()) { 11127 mMinHeight = nsViewportInfo::kExtendToZoom; 11128 if (aHeightString.EqualsLiteral("device-height")) { 11129 mMaxHeight = nsViewportInfo::kDeviceSize; 11130 } else { 11131 nsresult heightErrorCode; 11132 mMaxHeight = aHeightString.ToInteger(&heightErrorCode); 11133 if (NS_FAILED(heightErrorCode)) { 11134 mMaxHeight = nsViewportInfo::kAuto; 11135 } else if (mMaxHeight >= 0.0f) { 11136 mMaxHeight = std::clamp(mMaxHeight, CSSCoord(1.0f), CSSCoord(10000.0f)); 11137 } else { 11138 mMaxHeight = nsViewportInfo::kAuto; 11139 } 11140 } 11141 } 11142 } 11143 11144 nsViewportInfo Document::GetViewportInfo(const ScreenIntSize& aDisplaySize) { 11145 MOZ_ASSERT(mPresShell); 11146 11147 // Compute the CSS-to-LayoutDevice pixel scale as the product of the 11148 // widget scale and the full zoom. 11149 nsPresContext* context = mPresShell->GetPresContext(); 11150 // When querying the full zoom, get it from the device context rather than 11151 // directly from the pres context, because the device context's value can 11152 // include an adjustment necessary to keep the number of app units per device 11153 // pixel an integer, and we want the adjusted value. 11154 float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0; 11155 fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom; 11156 CSSToLayoutDeviceScale layoutDeviceScale = 11157 context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1); 11158 11159 CSSToScreenScale defaultScale = 11160 layoutDeviceScale * LayoutDeviceToScreenScale(1.0); 11161 11162 auto* bc = GetBrowsingContext(); 11163 const bool inRDM = bc && bc->InRDMPane(); 11164 const bool ignoreMetaTag = [&] { 11165 if (!nsLayoutUtils::ShouldHandleMetaViewport(this)) { 11166 return true; 11167 } 11168 if (Fullscreen()) { 11169 // We ignore viewport meta tag etc when in fullscreen, see bug 1696717. 11170 return true; 11171 } 11172 if (inRDM && bc->ForceDesktopViewport()) { 11173 // We ignore meta viewport when devtools tells us to force desktop 11174 // viewport on RDM. 11175 return true; 11176 } 11177 return false; 11178 }(); 11179 11180 if (ignoreMetaTag) { 11181 return nsViewportInfo(aDisplaySize, defaultScale, 11182 nsLayoutUtils::AllowZoomingForDocument(this) 11183 ? nsViewportInfo::ZoomFlag::AllowZoom 11184 : nsViewportInfo::ZoomFlag::DisallowZoom, 11185 StaticPrefs::apz_allow_zooming_out() 11186 ? nsViewportInfo::ZoomBehaviour::Mobile 11187 : nsViewportInfo::ZoomBehaviour::Desktop); 11188 } 11189 11190 // Special behaviour for desktop mode, provided we are not on an about: page 11191 // or a PDF.js page. 11192 if (bc && bc->ForceDesktopViewport() && !IsAboutPage() && 11193 !nsContentUtils::IsPDFJS(NodePrincipal())) { 11194 CSSCoord viewportWidth = 11195 StaticPrefs::browser_viewport_desktopWidth() / fullZoom; 11196 // Do not use a desktop viewport size less wide than the display. 11197 CSSCoord displayWidth = (aDisplaySize / defaultScale).width; 11198 MOZ_LOG(MobileViewportManager::gLog, LogLevel::Debug, 11199 ("Desktop-mode viewport size: choosing the larger of display width " 11200 "(%f) and desktop width (%f)", 11201 displayWidth.value, viewportWidth.value)); 11202 viewportWidth = nsViewportInfo::Max(displayWidth, viewportWidth); 11203 CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth); 11204 float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width; 11205 CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio); 11206 ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit); 11207 return nsViewportInfo(fakeDesktopSize, scaleToFit, 11208 nsViewportInfo::ZoomFlag::AllowZoom, 11209 nsViewportInfo::ZoomBehaviour::Mobile, 11210 nsViewportInfo::AutoScaleFlag::AutoScale); 11211 } 11212 11213 // In cases where the width of the CSS viewport is less than or equal to the 11214 // width of the display (i.e. width <= device-width) then we disable 11215 // double-tap-to-zoom behaviour. See bug 941995 for details. 11216 11217 switch (mViewportType) { 11218 case DisplayWidthHeight: 11219 return nsViewportInfo(aDisplaySize, defaultScale, 11220 nsViewportInfo::ZoomFlag::AllowZoom, 11221 nsViewportInfo::ZoomBehaviour::Mobile); 11222 case Unknown: { 11223 // We might early exit if the viewport is empty. Even if we don't, 11224 // at the end of this case we'll note that it was empty. Later, when 11225 // we're using the cached values, this will trigger alternate code paths. 11226 if (!mLastModifiedViewportMetaData) { 11227 // If the docType specifies that we are on a site optimized for mobile, 11228 // then we want to return specially crafted defaults for the viewport 11229 // info. 11230 if (RefPtr<DocumentType> docType = GetDoctype()) { 11231 nsAutoString docId; 11232 docType->GetPublicId(docId); 11233 if ((docId.Find(u"WAP") != -1) || (docId.Find(u"Mobile") != -1) || 11234 (docId.Find(u"WML") != -1)) { 11235 // We're making an assumption that the docType can't change here 11236 mViewportType = DisplayWidthHeight; 11237 return nsViewportInfo(aDisplaySize, defaultScale, 11238 nsViewportInfo::ZoomFlag::AllowZoom, 11239 nsViewportInfo::ZoomBehaviour::Mobile); 11240 } 11241 } 11242 11243 nsAutoString handheldFriendly; 11244 GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly); 11245 if (handheldFriendly.EqualsLiteral("true")) { 11246 mViewportType = DisplayWidthHeight; 11247 return nsViewportInfo(aDisplaySize, defaultScale, 11248 nsViewportInfo::ZoomFlag::AllowZoom, 11249 nsViewportInfo::ZoomBehaviour::Mobile); 11250 } 11251 } 11252 11253 ViewportMetaData metaData = GetViewportMetaData(); 11254 11255 // Parse initial-scale, minimum-scale and maximum-scale. 11256 ParseScalesInViewportMetaData(metaData); 11257 11258 // Parse width and height properties 11259 // This function sets m{Min,Max}{Width,Height}. 11260 ParseWidthAndHeightInMetaViewport(metaData.mWidth, metaData.mHeight, 11261 mValidScaleFloat); 11262 11263 mAllowZoom = true; 11264 if ((metaData.mUserScalable.EqualsLiteral("0")) || 11265 (metaData.mUserScalable.EqualsLiteral("no")) || 11266 (metaData.mUserScalable.EqualsLiteral("false"))) { 11267 mAllowZoom = false; 11268 } 11269 11270 // Resolve viewport-fit value. 11271 // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor 11272 mViewportFit = ViewportFitType::Auto; 11273 if (!metaData.mViewportFit.IsEmpty()) { 11274 if (metaData.mViewportFit.EqualsLiteral("contain")) { 11275 mViewportFit = ViewportFitType::Contain; 11276 } else if (metaData.mViewportFit.EqualsLiteral("cover")) { 11277 mViewportFit = ViewportFitType::Cover; 11278 } 11279 } 11280 11281 mWidthStrEmpty = metaData.mWidth.IsEmpty(); 11282 11283 mViewportType = Specified; 11284 [[fallthrough]]; 11285 } 11286 case Specified: 11287 default: 11288 LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat; 11289 LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat; 11290 bool effectiveValidMaxScale = mValidMaxScale; 11291 11292 nsViewportInfo::ZoomFlag effectiveZoomFlag = 11293 mAllowZoom ? nsViewportInfo::ZoomFlag::AllowZoom 11294 : nsViewportInfo::ZoomFlag::DisallowZoom; 11295 if (StaticPrefs::browser_ui_zoom_force_user_scalable()) { 11296 // If the pref to force user-scalable is enabled, we ignore the values 11297 // from the meta-viewport tag for these properties and just assume they 11298 // allow the page to be scalable. Note in particular that this code is 11299 // in the "Specified" branch of the enclosing switch statement, so that 11300 // calls to GetViewportInfo always use the latest value of the 11301 // browser_ui_zoom_force_user_scalable pref. Other codepaths that 11302 // return nsViewportInfo instances are all consistent with 11303 // browser_ui_zoom_force_user_scalable() already. 11304 effectiveMinScale = ViewportMinScale(); 11305 effectiveMaxScale = ViewportMaxScale(); 11306 effectiveValidMaxScale = true; 11307 effectiveZoomFlag = nsViewportInfo::ZoomFlag::AllowZoom; 11308 } 11309 11310 // Returns extend-zoom value which is MIN(mScaleFloat, mScaleMaxFloat). 11311 auto ComputeExtendZoom = [&]() -> float { 11312 if (mValidScaleFloat && effectiveValidMaxScale) { 11313 return std::min(mScaleFloat.scale, effectiveMaxScale.scale); 11314 } 11315 if (mValidScaleFloat) { 11316 return mScaleFloat.scale; 11317 } 11318 if (effectiveValidMaxScale) { 11319 return effectiveMaxScale.scale; 11320 } 11321 return nsViewportInfo::kAuto; 11322 }; 11323 11324 // Resolving 'extend-to-zoom' 11325 // https://drafts.csswg.org/css-device-adapt/#resolve-extend-to-zoom 11326 float extendZoom = ComputeExtendZoom(); 11327 11328 CSSCoord minWidth = mMinWidth; 11329 CSSCoord maxWidth = mMaxWidth; 11330 CSSCoord minHeight = mMinHeight; 11331 CSSCoord maxHeight = mMaxHeight; 11332 11333 // aDisplaySize is in screen pixels; convert them to CSS pixels for the 11334 // viewport size. We need to use this scaled size for any clamping of 11335 // width or height. 11336 CSSSize displaySize = ScreenSize(aDisplaySize) / defaultScale; 11337 11338 // Our min and max width and height values are mostly as specified by 11339 // the viewport declaration, but we make an exception for max width. 11340 // Max width, if auto, and if there's no initial-scale, will be set 11341 // to a default size. This is to support legacy site design with no 11342 // viewport declaration, and to do that using the same scheme as 11343 // Chrome does, in order to maintain web compatibility. Since the 11344 // default size has a complicated calculation, we fixup the maxWidth 11345 // value after setting it, above. 11346 if (maxWidth == nsViewportInfo::kAuto && !mValidScaleFloat) { 11347 maxWidth = StaticPrefs::browser_viewport_desktopWidth(); 11348 // Divide by fullZoom to stretch CSS pixel size of viewport in order 11349 // to keep device pixel size unchanged after full zoom applied. 11350 // See bug 974242. 11351 maxWidth /= fullZoom; 11352 11353 // The fallback behaviour of using browser.viewport.desktopWidth 11354 // was designed with small screens in mind, where we are _expanding_ the 11355 // viewport width to this value. On wider displays (e.g. most tablets), 11356 // we would actually be _shrinking_ the viewport width to this value, 11357 // which we want to avoid because it constrains the viewport width 11358 // (and often results in a larger initial scale) unnecessarily. 11359 MOZ_LOG(MobileViewportManager::gLog, LogLevel::Debug, 11360 ("Fallback viewport size: choosing the larger of display width " 11361 "(%f) and desktop width (%f)", 11362 displaySize.width, maxWidth.value)); 11363 maxWidth = nsViewportInfo::Max(displaySize.width, maxWidth); 11364 11365 // We set minWidth to ExtendToZoom, which will cause our later width 11366 // calculation to expand to maxWidth, if scale restrictions allow it. 11367 minWidth = nsViewportInfo::kExtendToZoom; 11368 } 11369 11370 // Resolve device-width and device-height first. 11371 if (maxWidth == nsViewportInfo::kDeviceSize) { 11372 maxWidth = displaySize.width; 11373 } 11374 if (maxHeight == nsViewportInfo::kDeviceSize) { 11375 maxHeight = displaySize.height; 11376 } 11377 if (extendZoom == nsViewportInfo::kAuto) { 11378 if (maxWidth == nsViewportInfo::kExtendToZoom) { 11379 maxWidth = nsViewportInfo::kAuto; 11380 } 11381 if (maxHeight == nsViewportInfo::kExtendToZoom) { 11382 maxHeight = nsViewportInfo::kAuto; 11383 } 11384 if (minWidth == nsViewportInfo::kExtendToZoom) { 11385 minWidth = maxWidth; 11386 } 11387 if (minHeight == nsViewportInfo::kExtendToZoom) { 11388 minHeight = maxHeight; 11389 } 11390 } else { 11391 CSSSize extendSize = displaySize / extendZoom; 11392 if (maxWidth == nsViewportInfo::kExtendToZoom) { 11393 maxWidth = extendSize.width; 11394 } 11395 if (maxHeight == nsViewportInfo::kExtendToZoom) { 11396 maxHeight = extendSize.height; 11397 } 11398 if (minWidth == nsViewportInfo::kExtendToZoom) { 11399 minWidth = nsViewportInfo::Max(extendSize.width, maxWidth); 11400 } 11401 if (minHeight == nsViewportInfo::kExtendToZoom) { 11402 minHeight = nsViewportInfo::Max(extendSize.height, maxHeight); 11403 } 11404 } 11405 11406 // Resolve initial width and height from min/max descriptors 11407 // https://drafts.csswg.org/css-device-adapt/#resolve-initial-width-height 11408 CSSCoord width = nsViewportInfo::kAuto; 11409 if (minWidth != nsViewportInfo::kAuto || 11410 maxWidth != nsViewportInfo::kAuto) { 11411 width = nsViewportInfo::Max( 11412 minWidth, nsViewportInfo::Min(maxWidth, displaySize.width)); 11413 } 11414 CSSCoord height = nsViewportInfo::kAuto; 11415 if (minHeight != nsViewportInfo::kAuto || 11416 maxHeight != nsViewportInfo::kAuto) { 11417 height = nsViewportInfo::Max( 11418 minHeight, nsViewportInfo::Min(maxHeight, displaySize.height)); 11419 } 11420 11421 // Resolve width value 11422 // https://drafts.csswg.org/css-device-adapt/#resolve-width 11423 if (width == nsViewportInfo::kAuto) { 11424 if (height == nsViewportInfo::kAuto || aDisplaySize.height == 0) { 11425 width = displaySize.width; 11426 } else { 11427 width = height * aDisplaySize.width / aDisplaySize.height; 11428 } 11429 } 11430 11431 // Resolve height value 11432 // https://drafts.csswg.org/css-device-adapt/#resolve-height 11433 if (height == nsViewportInfo::kAuto) { 11434 if (aDisplaySize.width == 0) { 11435 height = displaySize.height; 11436 } else { 11437 height = width * aDisplaySize.height / aDisplaySize.width; 11438 } 11439 } 11440 MOZ_ASSERT(width != nsViewportInfo::kAuto && 11441 height != nsViewportInfo::kAuto); 11442 11443 CSSSize size(width, height); 11444 11445 CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale; 11446 CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale; 11447 CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale; 11448 11449 nsViewportInfo::AutoSizeFlag sizeFlag = 11450 nsViewportInfo::AutoSizeFlag::FixedSize; 11451 if (mMaxWidth == nsViewportInfo::kDeviceSize || 11452 (mWidthStrEmpty && (mMaxHeight == nsViewportInfo::kDeviceSize || 11453 mScaleFloat.scale == 1.0f)) || 11454 (!mWidthStrEmpty && mMaxWidth == nsViewportInfo::kAuto && 11455 mMaxHeight < 0)) { 11456 sizeFlag = nsViewportInfo::AutoSizeFlag::AutoSize; 11457 } 11458 11459 // FIXME: Resolving width and height should be done above 'Resolve width 11460 // value' and 'Resolve height value'. 11461 if (sizeFlag == nsViewportInfo::AutoSizeFlag::AutoSize) { 11462 size = displaySize; 11463 } 11464 11465 // The purpose of clamping the viewport width to a minimum size is to 11466 // prevent page authors from setting it to a ridiculously small value. 11467 // If the page is actually being rendered in a very small area (as might 11468 // happen in e.g. Android 8's picture-in-picture mode), we don't want to 11469 // prevent the viewport from taking on that size. 11470 CSSSize effectiveMinSize = Min(CSSSize(kViewportMinSize), displaySize); 11471 11472 size.width = std::clamp(size.width, effectiveMinSize.width, 11473 float(kViewportMaxSize.width)); 11474 11475 // Also recalculate the default zoom, if it wasn't specified in the 11476 // metadata, and the width is specified. 11477 if (!mValidScaleFloat && !mWidthStrEmpty) { 11478 CSSToScreenScale bestFitScale(float(aDisplaySize.width) / size.width); 11479 scaleFloat = (scaleFloat > bestFitScale) ? scaleFloat : bestFitScale; 11480 } 11481 11482 size.height = std::clamp(size.height, effectiveMinSize.height, 11483 float(kViewportMaxSize.height)); 11484 11485 // In cases of user-scalable=no, if we have a positive scale, clamp it to 11486 // min and max, and then use the clamped value for the scale, the min, and 11487 // the max. If we don't have a positive scale, assert that we are setting 11488 // the auto scale flag. 11489 if (effectiveZoomFlag == nsViewportInfo::ZoomFlag::DisallowZoom && 11490 scaleFloat > CSSToScreenScale(0.0f)) { 11491 scaleFloat = scaleMinFloat = scaleMaxFloat = 11492 std::clamp(scaleFloat, scaleMinFloat, scaleMaxFloat); 11493 } 11494 MOZ_ASSERT( 11495 scaleFloat > CSSToScreenScale(0.0f) || !mValidScaleFloat, 11496 "If we don't have a positive scale, we should be using auto scale."); 11497 11498 // We need to perform a conversion, but only if the initial or maximum 11499 // scale were set explicitly by the user. 11500 if (mValidScaleFloat && scaleFloat >= scaleMinFloat && 11501 scaleFloat <= scaleMaxFloat) { 11502 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat; 11503 size.width = std::max(size.width, displaySize.width); 11504 size.height = std::max(size.height, displaySize.height); 11505 } else if (effectiveValidMaxScale) { 11506 CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat; 11507 size.width = std::max(size.width, displaySize.width); 11508 size.height = std::max(size.height, displaySize.height); 11509 } 11510 11511 return nsViewportInfo( 11512 scaleFloat, scaleMinFloat, scaleMaxFloat, size, sizeFlag, 11513 mValidScaleFloat ? nsViewportInfo::AutoScaleFlag::FixedScale 11514 : nsViewportInfo::AutoScaleFlag::AutoScale, 11515 effectiveZoomFlag, mViewportFit); 11516 } 11517 } 11518 11519 ViewportMetaData Document::GetViewportMetaData() const { 11520 return mLastModifiedViewportMetaData ? *mLastModifiedViewportMetaData 11521 : ViewportMetaData(); 11522 } 11523 11524 static InteractiveWidget ParseInteractiveWidget( 11525 const ViewportMetaData& aViewportMetaData) { 11526 if (aViewportMetaData.mInteractiveWidgetMode.IsEmpty()) { 11527 return InteractiveWidgetUtils::DefaultInteractiveWidgetMode(); 11528 } 11529 11530 if (aViewportMetaData.mInteractiveWidgetMode.EqualsIgnoreCase( 11531 "resizes-visual")) { 11532 return InteractiveWidget::ResizesVisual; 11533 } 11534 if (aViewportMetaData.mInteractiveWidgetMode.EqualsIgnoreCase( 11535 "resizes-content")) { 11536 return InteractiveWidget::ResizesContent; 11537 } 11538 if (aViewportMetaData.mInteractiveWidgetMode.EqualsIgnoreCase( 11539 "overlays-content")) { 11540 return InteractiveWidget::OverlaysContent; 11541 } 11542 return InteractiveWidgetUtils::DefaultInteractiveWidgetMode(); 11543 } 11544 11545 void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) { 11546 mLastModifiedViewportMetaData = std::move(aData); 11547 // Trigger recomputation of the nsViewportInfo the next time it's queried. 11548 mViewportType = Unknown; 11549 11550 // Parse interactive-widget here anyway. Normally we parse any data in the 11551 // meta viewport tag in GetViewportInfo(), but GetViewportInfo() depends on 11552 // the document state (e.g. display size, fullscreen, desktop-mode etc.) 11553 // whereas interactive-widget is independent from the document state, it's 11554 // necessary whatever the document state is. 11555 dom::InteractiveWidget interactiveWidget = 11556 ParseInteractiveWidget(*mLastModifiedViewportMetaData); 11557 if (mInteractiveWidgetMode != interactiveWidget) { 11558 mInteractiveWidgetMode = interactiveWidget; 11559 } 11560 11561 AsyncEventDispatcher::RunDOMEventWhenSafe( 11562 *this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes, 11563 ChromeOnlyDispatch::eYes); 11564 } 11565 11566 EventListenerManager* Document::GetOrCreateListenerManager() { 11567 if (!mListenerManager) { 11568 mListenerManager = 11569 new EventListenerManager(static_cast<EventTarget*>(this)); 11570 SetFlags(NODE_HAS_LISTENERMANAGER); 11571 } 11572 11573 return mListenerManager; 11574 } 11575 11576 EventListenerManager* Document::GetExistingListenerManager() const { 11577 return mListenerManager; 11578 } 11579 11580 void Document::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 11581 aVisitor.mCanHandle = true; 11582 // FIXME! This is a hack to make middle mouse paste working also in Editor. 11583 // Bug 329119 11584 aVisitor.mForceContentDispatch = true; 11585 11586 // Load events must not propagate to |window| object, see bug 335251. 11587 if (aVisitor.mEvent->mMessage != eLoad) { 11588 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow()); 11589 aVisitor.SetParentTarget( 11590 window ? window->GetTargetForEventTargetChain() : nullptr, false); 11591 } 11592 } 11593 11594 already_AddRefed<Event> Document::CreateEvent(const nsAString& aEventType, 11595 CallerType aCallerType, 11596 ErrorResult& rv) const { 11597 nsPresContext* presContext = GetPresContext(); 11598 11599 // Create event even without presContext. 11600 RefPtr<Event> ev = 11601 EventDispatcher::CreateEvent(const_cast<Document*>(this), presContext, 11602 nullptr, aEventType, aCallerType); 11603 if (!ev) { 11604 rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 11605 return nullptr; 11606 } 11607 WidgetEvent* e = ev->WidgetEventPtr(); 11608 e->mFlags.mBubbles = false; 11609 e->mFlags.mCancelable = false; 11610 return ev.forget(); 11611 } 11612 11613 void Document::FlushPendingNotifications(FlushType aType) { 11614 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style, 11615 aType >= FlushType::Layout); 11616 FlushPendingNotifications(flush); 11617 } 11618 11619 void Document::FlushPendingNotifications(mozilla::ChangesToFlush aFlush) { 11620 FlushType flushType = aFlush.mFlushType; 11621 11622 RefPtr<Document> documentOnStack = this; 11623 11624 // We need to flush the sink for non-HTML documents (because the XML 11625 // parser still does insertion with deferred notifications). We 11626 // also need to flush the sink if this is a layout-related flush, to 11627 // make sure that layout is started as needed. But we can skip that 11628 // part if we have no presshell or if it's already done an initial 11629 // reflow. 11630 if ((!IsHTMLDocument() || (flushType > FlushType::ContentAndNotify && 11631 mPresShell && !mPresShell->DidInitialize())) && 11632 (mParser || mWeakSink)) { 11633 nsCOMPtr<nsIContentSink> sink; 11634 if (mParser) { 11635 sink = mParser->GetContentSink(); 11636 } else { 11637 sink = do_QueryReferent(mWeakSink); 11638 if (!sink) { 11639 mWeakSink = nullptr; 11640 } 11641 } 11642 // Determine if it is safe to flush the sink notifications 11643 // by determining if it safe to flush all the presshells. 11644 if (sink && (flushType == FlushType::Content || IsSafeToFlush())) { 11645 sink->FlushPendingNotifications(flushType); 11646 } 11647 } 11648 11649 // Should we be flushing pending binding constructors in here? 11650 11651 if (flushType <= FlushType::ContentAndNotify) { 11652 // Nothing to do here 11653 return; 11654 } 11655 11656 // If we have a parent we must flush the parent too to ensure that our 11657 // container is reflowed if its size was changed. 11658 // 11659 // We do it only if the subdocument and the parent can observe each other 11660 // synchronously (that is, if we're not cross-origin), to avoid work that is 11661 // not observable, and if the parent document has finished loading all its 11662 // render-blocking stylesheets and may start laying out the document, to avoid 11663 // unnecessary flashes of unstyled content on the parent document. Note that 11664 // this last bit means that size-dependent media queries in this document may 11665 // produce incorrect results temporarily. 11666 // 11667 // But if it's not safe to flush ourselves, then don't flush the parent, since 11668 // that can cause things like resizes of our frame's widget, which we can't 11669 // handle while flushing is unsafe. 11670 if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() && 11671 mParentDocument->MayStartLayout() && IsSafeToFlush()) { 11672 ChangesToFlush parentFlush = aFlush; 11673 if (flushType >= FlushType::Style) { 11674 // Since media queries mean that a size change of our container can affect 11675 // style, we need to promote a style flush on ourself to a layout flush on 11676 // our parent, since we need our container to be the correct size to 11677 // determine the correct style. 11678 parentFlush.mFlushType = std::max(FlushType::Layout, flushType); 11679 } 11680 mParentDocument->FlushPendingNotifications(parentFlush); 11681 } 11682 11683 if (RefPtr<PresShell> presShell = GetPresShell()) { 11684 presShell->FlushPendingNotifications(aFlush); 11685 } 11686 } 11687 11688 void Document::FlushExternalResources(FlushType aType) { 11689 NS_ASSERTION( 11690 aType >= FlushType::Style, 11691 "should only need to flush for style or higher in external resources"); 11692 if (GetDisplayDocument()) { 11693 return; 11694 } 11695 11696 EnumerateExternalResources([aType](Document& aDoc) { 11697 aDoc.FlushPendingNotifications(aType); 11698 return CallState::Continue; 11699 }); 11700 } 11701 11702 void Document::SetXMLDeclaration(const char16_t* aVersion, 11703 const char16_t* aEncoding, 11704 const int32_t aStandalone) { 11705 if (!aVersion || *aVersion == '\0') { 11706 mXMLDeclarationBits = 0; 11707 return; 11708 } 11709 11710 mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS; 11711 11712 if (aEncoding && *aEncoding != '\0') { 11713 mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS; 11714 } 11715 11716 if (aStandalone == 1) { 11717 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS | 11718 XML_DECLARATION_BITS_STANDALONE_YES; 11719 } else if (aStandalone == 0) { 11720 mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS; 11721 } 11722 } 11723 11724 void Document::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding, 11725 nsAString& aStandalone) { 11726 aVersion.Truncate(); 11727 aEncoding.Truncate(); 11728 aStandalone.Truncate(); 11729 11730 if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) { 11731 return; 11732 } 11733 11734 // always until we start supporting 1.1 etc. 11735 aVersion.AssignLiteral("1.0"); 11736 11737 if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) { 11738 // This is what we have stored, not necessarily what was written 11739 // in the original 11740 GetCharacterSet(aEncoding); 11741 } 11742 11743 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) { 11744 if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) { 11745 aStandalone.AssignLiteral("yes"); 11746 } else { 11747 aStandalone.AssignLiteral("no"); 11748 } 11749 } 11750 } 11751 11752 void Document::AddColorSchemeMeta(HTMLMetaElement& aMeta) { 11753 mColorSchemeMetaTags.Insert(aMeta); 11754 RecomputeColorScheme(); 11755 } 11756 11757 void Document::RemoveColorSchemeMeta(HTMLMetaElement& aMeta) { 11758 mColorSchemeMetaTags.RemoveElement(aMeta); 11759 RecomputeColorScheme(); 11760 } 11761 11762 void Document::RecomputeColorScheme() { 11763 auto oldColorScheme = mColorSchemeBits; 11764 mColorSchemeBits = 0; 11765 for (const HTMLMetaElement* el : mColorSchemeMetaTags.AsSpan()) { 11766 nsAutoString content; 11767 if (!el->GetAttr(nsGkAtoms::content, content)) { 11768 continue; 11769 } 11770 11771 NS_ConvertUTF16toUTF8 contentU8(content); 11772 if (Servo_ColorScheme_Parse(&contentU8, &mColorSchemeBits)) { 11773 break; 11774 } 11775 } 11776 11777 if (mColorSchemeBits == oldColorScheme) { 11778 return; 11779 } 11780 11781 if (nsPresContext* pc = GetPresContext()) { 11782 // This affects system colors, which are inherited, so we need to recascade. 11783 pc->RebuildAllStyleData(nsChangeHint(0), RestyleHint::RecascadeSubtree()); 11784 } 11785 } 11786 11787 bool Document::IsScriptEnabled() const { 11788 // If this document is sandboxed without 'allow-scripts' 11789 // script is not enabled 11790 if (HasScriptsBlockedBySandbox()) { 11791 return false; 11792 } 11793 11794 nsCOMPtr<nsIScriptGlobalObject> globalObject = 11795 do_QueryInterface(GetInnerWindow()); 11796 if (!globalObject || !globalObject->HasJSGlobal()) { 11797 return false; 11798 } 11799 11800 return xpc::Scriptability::Get(globalObject->GetGlobalJSObjectPreserveColor()) 11801 .Allowed(); 11802 } 11803 11804 void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) { 11805 PRTime modDate = 0; 11806 nsresult rv; 11807 11808 nsCOMPtr<nsIHttpChannel> httpChannel; 11809 rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); 11810 if (NS_WARN_IF(NS_FAILED(rv))) { 11811 return; 11812 } 11813 11814 if (httpChannel) { 11815 nsAutoCString tmp; 11816 rv = httpChannel->GetResponseHeader("last-modified"_ns, tmp); 11817 11818 if (NS_SUCCEEDED(rv)) { 11819 PRTime time; 11820 PRStatus st = PR_ParseTimeString(tmp.get(), true, &time); 11821 if (st == PR_SUCCESS) { 11822 modDate = time; 11823 } 11824 } 11825 11826 static const char* const headers[] = { 11827 "default-style", "content-style-type", "content-language", 11828 "content-disposition", "refresh", "x-dns-prefetch-control", 11829 "x-frame-options", "origin-trial", "onion-location", 11830 // add more http headers if you need 11831 // XXXbz don't add content-location support without reading bug 11832 // 238654 and its dependencies/dups first. 11833 0}; 11834 11835 nsAutoCString headerVal; 11836 const char* const* name = headers; 11837 while (*name) { 11838 rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal); 11839 if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) { 11840 RefPtr<nsAtom> key = NS_Atomize(*name); 11841 SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal)); 11842 } 11843 ++name; 11844 } 11845 } else { 11846 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel); 11847 if (fileChannel) { 11848 nsCOMPtr<nsIFile> file; 11849 fileChannel->GetFile(getter_AddRefs(file)); 11850 if (file) { 11851 PRTime msecs; 11852 rv = file->GetLastModifiedTime(&msecs); 11853 11854 if (NS_SUCCEEDED(rv)) { 11855 modDate = msecs * int64_t(PR_USEC_PER_MSEC); 11856 } 11857 } 11858 } else { 11859 nsAutoCString contentDisp; 11860 rv = aChannel->GetContentDispositionHeader(contentDisp); 11861 if (NS_SUCCEEDED(rv)) { 11862 SetHeaderData(nsGkAtoms::headerContentDisposition, 11863 NS_ConvertASCIItoUTF16(contentDisp)); 11864 } 11865 } 11866 } 11867 11868 mLastModified.Truncate(); 11869 if (modDate != 0) { 11870 GetFormattedTimeString(modDate, 11871 ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC), 11872 mLastModified); 11873 } 11874 } 11875 11876 void Document::ProcessMETATag(HTMLMetaElement* aMetaElement) { 11877 // set any HTTP-EQUIV data into document's header data as well as url 11878 nsAutoString header; 11879 aMetaElement->GetAttr(nsGkAtoms::httpEquiv, header); 11880 if (!header.IsEmpty()) { 11881 // Ignore META REFRESH when document is sandboxed from automatic features. 11882 nsContentUtils::ASCIIToLower(header); 11883 if (nsGkAtoms::refresh->Equals(header) && 11884 (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) { 11885 return; 11886 } 11887 11888 nsAutoString result; 11889 aMetaElement->GetAttr(nsGkAtoms::content, result); 11890 if (!result.IsEmpty()) { 11891 RefPtr<nsAtom> fieldAtom(NS_Atomize(header)); 11892 SetHeaderData(fieldAtom, result); 11893 } 11894 } 11895 11896 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, 11897 nsGkAtoms::handheldFriendly, eIgnoreCase)) { 11898 nsAutoString result; 11899 aMetaElement->GetAttr(nsGkAtoms::content, result); 11900 if (!result.IsEmpty()) { 11901 nsContentUtils::ASCIIToLower(result); 11902 SetHeaderData(nsGkAtoms::handheldFriendly, result); 11903 } 11904 } 11905 // Check for Restricted To Adults meta tag 11906 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, 11907 nsGkAtoms::rating, eIgnoreCase)) { 11908 if (aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::content, 11909 nsGkAtoms::adult, eIgnoreCase) || 11910 aMetaElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::content, 11911 nsGkAtoms::restrictToAdults, eIgnoreCase)) { 11912 BrowsingContext* bc = GetBrowsingContext(); 11913 if (bc && bc->GetParentalControlsEnabled() && GetDocShell()) { 11914 RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell()); 11915 nsCOMPtr<nsIRunnable> redirect = NewRunnableMethod( 11916 "Document::ProcessMETATag::DisplayRestrictedContentError", docShell, 11917 &nsDocShell::DisplayRestrictedContentError); 11918 nsContentUtils::AddScriptRunner(redirect.forget()); 11919 } 11920 } 11921 } 11922 } 11923 11924 void Document::TerminateParserAndDisableScripts() { 11925 if (mParser) { 11926 (void)mParser->Terminate(); 11927 MOZ_ASSERT(!mParser, "mParser should have been null'd out"); 11928 } 11929 11930 if (WindowContext* wc = GetWindowContext()) { 11931 (void)wc->SetAllowJavascript(false); 11932 } 11933 } 11934 11935 already_AddRefed<Element> Document::CreateElem(const nsAString& aName, 11936 nsAtom* aPrefix, 11937 int32_t aNamespaceID, 11938 const nsAString* aIs) { 11939 #ifdef DEBUG 11940 nsAutoString qName; 11941 if (aPrefix) { 11942 aPrefix->ToString(qName); 11943 qName.Append(':'); 11944 } 11945 qName.Append(aName); 11946 11947 // Note: "a:b:c" is a valid name in non-namespaces XML, and 11948 // Document::CreateElement can call us with such a name and no prefix, 11949 // which would cause an error if we just used true here. 11950 bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID(); 11951 NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)), 11952 "Don't pass invalid prefixes to Document::CreateElem, " 11953 "check caller."); 11954 #endif 11955 11956 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 11957 mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, ELEMENT_NODE, 11958 getter_AddRefs(nodeInfo)); 11959 NS_ENSURE_TRUE(nodeInfo, nullptr); 11960 11961 nsCOMPtr<Element> element; 11962 nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(), 11963 NOT_FROM_PARSER, aIs); 11964 return NS_SUCCEEDED(rv) ? element.forget() : nullptr; 11965 } 11966 11967 bool Document::IsSafeToFlush() const { 11968 PresShell* presShell = GetPresShell(); 11969 if (!presShell) { 11970 return true; 11971 } 11972 return presShell->IsSafeToFlush(); 11973 } 11974 11975 void Document::Sanitize() { 11976 // Sanitize the document by resetting all (current and former) password fields 11977 // and any form fields with autocomplete=off to their default values. We do 11978 // this now, instead of when the presentation is restored, to offer some 11979 // protection in case there is ever an exploit that allows a cached document 11980 // to be accessed from a different document. 11981 11982 // First locate all input elements, regardless of whether they are 11983 // in a form, and reset the password and autocomplete=off elements. 11984 11985 RefPtr<nsContentList> nodes = GetElementsByTagName(u"input"_ns); 11986 11987 nsAutoString value; 11988 11989 uint32_t length = nodes->Length(true); 11990 for (uint32_t i = 0; i < length; ++i) { 11991 NS_ASSERTION(nodes->Item(i), "null item in node list!"); 11992 11993 RefPtr<HTMLInputElement> input = 11994 HTMLInputElement::FromNodeOrNull(nodes->Item(i)); 11995 if (!input) continue; 11996 11997 input->GetAttr(nsGkAtoms::autocomplete, value); 11998 if (value.LowerCaseEqualsLiteral("off") || input->HasBeenTypePassword()) { 11999 input->Reset(); 12000 } 12001 } 12002 12003 // Now locate all _form_ elements that have autocomplete=off and reset them 12004 nodes = GetElementsByTagName(u"form"_ns); 12005 12006 length = nodes->Length(true); 12007 for (uint32_t i = 0; i < length; ++i) { 12008 // Reset() may change the list dynamically. 12009 RefPtr<HTMLFormElement> form = 12010 HTMLFormElement::FromNodeOrNull(nodes->Item(i)); 12011 if (!form) continue; 12012 12013 form->GetAttr(nsGkAtoms::autocomplete, value); 12014 if (value.LowerCaseEqualsLiteral("off")) form->Reset(); 12015 } 12016 } 12017 12018 void Document::EnumerateSubDocuments(SubDocEnumFunc aCallback) { 12019 if (!mSubDocuments) { 12020 return; 12021 } 12022 12023 // PLDHashTable::Iterator can't handle modifications while iterating so we 12024 // copy all entries to an array first before calling any callbacks. 12025 AutoTArray<RefPtr<Document>, 8> subdocs; 12026 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) { 12027 const auto* entry = static_cast<SubDocMapEntry*>(iter.Get()); 12028 if (Document* subdoc = entry->mSubDocument) { 12029 subdocs.AppendElement(subdoc); 12030 } 12031 } 12032 for (auto& subdoc : subdocs) { 12033 if (aCallback(*subdoc) == CallState::Stop) { 12034 break; 12035 } 12036 } 12037 } 12038 12039 void Document::CollectDescendantDocuments( 12040 nsTArray<RefPtr<Document>>& aDescendants, 12041 IncludeSubResources aIncludeSubresources, SubDocTestFunc aCallback) const { 12042 if (mSubDocuments) { 12043 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) { 12044 const auto* entry = static_cast<SubDocMapEntry*>(iter.Get()); 12045 if (Document* subdoc = entry->mSubDocument) { 12046 if (aCallback(subdoc)) { 12047 aDescendants.AppendElement(subdoc); 12048 } 12049 subdoc->CollectDescendantDocuments(aDescendants, aIncludeSubresources, 12050 aCallback); 12051 } 12052 } 12053 } 12054 12055 if (aIncludeSubresources == IncludeSubResources::Yes) { 12056 mExternalResourceMap.CollectDescendantDocuments(aDescendants, aCallback); 12057 } 12058 } 12059 12060 bool Document::CanSavePresentation(nsIRequest* aNewRequest, 12061 uint32_t& aBFCacheCombo, 12062 bool aIncludeSubdocuments, 12063 bool aAllowUnloadListeners) { 12064 bool ret = true; 12065 12066 if (!IsBFCachingAllowed()) { 12067 aBFCacheCombo |= BFCacheStatus::NOT_ALLOWED; 12068 ret = false; 12069 } 12070 12071 nsAutoCString uri; 12072 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) { 12073 if (mDocumentURI) { 12074 mDocumentURI->GetSpec(uri); 12075 } 12076 } 12077 12078 if (EventHandlingSuppressed()) { 12079 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12080 ("Save of %s blocked on event handling suppression", uri.get())); 12081 aBFCacheCombo |= BFCacheStatus::EVENT_HANDLING_SUPPRESSED; 12082 ret = false; 12083 } 12084 12085 // Do not allow suspended windows to be placed in the 12086 // bfcache. This method is also used to verify a document 12087 // coming out of the bfcache is ok to restore, though. So 12088 // we only want to block suspend windows that aren't also 12089 // frozen. 12090 auto* win = nsGlobalWindowInner::Cast(GetInnerWindow()); 12091 if (win && win->IsSuspended() && !win->IsFrozen()) { 12092 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12093 ("Save of %s blocked on suspended Window", uri.get())); 12094 aBFCacheCombo |= BFCacheStatus::SUSPENDED; 12095 ret = false; 12096 } 12097 12098 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aNewRequest); 12099 bool thirdParty = false; 12100 // Currently some other mobile browsers seem to bfcache only cross-domain 12101 // pages, but bfcache those also when there are unload event listeners, so 12102 // this is trying to match that behavior as much as possible. 12103 bool allowUnloadListeners = 12104 aAllowUnloadListeners && 12105 StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners() && 12106 (!channel || (NS_SUCCEEDED(NodePrincipal()->IsThirdPartyChannel( 12107 channel, &thirdParty)) && 12108 thirdParty)); 12109 12110 // Check our event listener manager for unload/beforeunload listeners. 12111 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject); 12112 if (!allowUnloadListeners && piTarget) { 12113 EventListenerManager* manager = piTarget->GetExistingListenerManager(); 12114 if (manager) { 12115 if (manager->HasUnloadListeners()) { 12116 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12117 ("Save of %s blocked due to unload handlers", uri.get())); 12118 aBFCacheCombo |= BFCacheStatus::UNLOAD_LISTENER; 12119 ret = false; 12120 } 12121 if (manager->HasBeforeUnloadListeners()) { 12122 if (!mozilla::SessionHistoryInParent() || 12123 !StaticPrefs:: 12124 docshell_shistory_bfcache_ship_allow_beforeunload_listeners()) { 12125 MOZ_LOG( 12126 gPageCacheLog, mozilla::LogLevel::Verbose, 12127 ("Save of %s blocked due to beforeUnload handlers", uri.get())); 12128 aBFCacheCombo |= BFCacheStatus::BEFOREUNLOAD_LISTENER; 12129 ret = false; 12130 } 12131 } 12132 } 12133 } 12134 12135 // Check if we have pending network requests 12136 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup(); 12137 if (loadGroup) { 12138 nsCOMPtr<nsISimpleEnumerator> requests; 12139 loadGroup->GetRequests(getter_AddRefs(requests)); 12140 12141 bool hasMore = false; 12142 12143 // We want to bail out if we have any requests other than aNewRequest (or 12144 // in the case when aNewRequest is a part of a multipart response the base 12145 // channel the multipart response is coming in on). 12146 nsCOMPtr<nsIChannel> baseChannel; 12147 nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest)); 12148 if (part) { 12149 part->GetBaseChannel(getter_AddRefs(baseChannel)); 12150 } 12151 12152 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) { 12153 nsCOMPtr<nsISupports> elem; 12154 requests->GetNext(getter_AddRefs(elem)); 12155 12156 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem); 12157 if (request && request != aNewRequest && request != baseChannel) { 12158 // Favicon loads don't need to block caching. 12159 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); 12160 if (channel) { 12161 nsCOMPtr<nsILoadInfo> li = channel->LoadInfo(); 12162 if (li->InternalContentPolicyType() == 12163 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) { 12164 continue; 12165 } 12166 } 12167 12168 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) { 12169 nsAutoCString requestName; 12170 request->GetName(requestName); 12171 MOZ_LOG(gPageCacheLog, LogLevel::Verbose, 12172 ("Save of %s blocked because document has request %s", 12173 uri.get(), requestName.get())); 12174 } 12175 aBFCacheCombo |= BFCacheStatus::REQUEST; 12176 ret = false; 12177 } 12178 } 12179 } 12180 12181 // Check if we have active GetUserMedia use 12182 if (MediaManager::Exists() && win && 12183 MediaManager::Get()->IsWindowStillActive(win->WindowID())) { 12184 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12185 ("Save of %s blocked due to GetUserMedia", uri.get())); 12186 aBFCacheCombo |= BFCacheStatus::ACTIVE_GET_USER_MEDIA; 12187 ret = false; 12188 } 12189 12190 #ifdef MOZ_WEBRTC 12191 // Check if we have active PeerConnections 12192 if (win && win->HasActivePeerConnections()) { 12193 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12194 ("Save of %s blocked due to PeerConnection", uri.get())); 12195 aBFCacheCombo |= BFCacheStatus::ACTIVE_PEER_CONNECTION; 12196 ret = false; 12197 } 12198 #endif // MOZ_WEBRTC 12199 12200 // Don't save presentations for documents containing EME content, so that 12201 // CDMs reliably shutdown upon user navigation. 12202 if (ContainsEMEContent()) { 12203 aBFCacheCombo |= BFCacheStatus::CONTAINS_EME_CONTENT; 12204 ret = false; 12205 } 12206 12207 // Don't save presentations for documents containing MSE content, to 12208 // reduce memory usage. 12209 if (ContainsMSEContent()) { 12210 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12211 ("Save of %s blocked due to MSE use", uri.get())); 12212 aBFCacheCombo |= BFCacheStatus::CONTAINS_MSE_CONTENT; 12213 ret = false; 12214 } 12215 12216 if (aIncludeSubdocuments && mSubDocuments) { 12217 for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) { 12218 auto entry = static_cast<SubDocMapEntry*>(iter.Get()); 12219 Document* subdoc = entry->mSubDocument; 12220 12221 uint32_t subDocBFCacheCombo = 0; 12222 // The aIgnoreRequest we were passed is only for us, so don't pass it on. 12223 bool canCache = 12224 subdoc ? subdoc->CanSavePresentation(nullptr, subDocBFCacheCombo, 12225 true, allowUnloadListeners) 12226 : false; 12227 if (!canCache) { 12228 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12229 ("Save of %s blocked due to subdocument blocked", uri.get())); 12230 aBFCacheCombo |= subDocBFCacheCombo; 12231 ret = false; 12232 } 12233 } 12234 } 12235 12236 if (!mozilla::BFCacheInParent()) { 12237 // BFCache is currently not compatible with remote subframes (bug 1609324) 12238 if (RefPtr<BrowsingContext> browsingContext = GetBrowsingContext()) { 12239 for (auto& child : browsingContext->Children()) { 12240 if (!child->IsInProcess()) { 12241 aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES; 12242 ret = false; 12243 break; 12244 } 12245 } 12246 } 12247 } 12248 12249 if (win) { 12250 auto* globalWindow = nsGlobalWindowInner::Cast(win); 12251 #ifdef MOZ_WEBSPEECH 12252 if (globalWindow->HasActiveSpeechSynthesis()) { 12253 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12254 ("Save of %s blocked due to Speech use", uri.get())); 12255 aBFCacheCombo |= BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS; 12256 ret = false; 12257 } 12258 #endif 12259 if (globalWindow->HasUsedVR()) { 12260 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12261 ("Save of %s blocked due to having used VR", uri.get())); 12262 aBFCacheCombo |= BFCacheStatus::HAS_USED_VR; 12263 ret = false; 12264 } 12265 12266 if (win->HasActiveLocks()) { 12267 MOZ_LOG( 12268 gPageCacheLog, mozilla::LogLevel::Verbose, 12269 ("Save of %s blocked due to having active lock requests", uri.get())); 12270 aBFCacheCombo |= BFCacheStatus::ACTIVE_LOCK; 12271 ret = false; 12272 } 12273 12274 if (win->HasActiveWebTransports()) { 12275 MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, 12276 ("Save of %s blocked due to WebTransport", uri.get())); 12277 aBFCacheCombo |= BFCacheStatus::ACTIVE_WEBTRANSPORT; 12278 ret = false; 12279 } 12280 } 12281 12282 return ret; 12283 } 12284 12285 // https://wicg.github.io/document-picture-in-picture/#close-any-associated-document-picture-in-picture-windows 12286 void Document::CloseAnyAssociatedDocumentPiPWindows() { 12287 BrowsingContext* bc = GetBrowsingContext(); 12288 if (!bc || !bc->IsTop()) { 12289 return; 12290 } 12291 12292 // 3. Close us if we're a PIP window 12293 // Note that this method is called when the opener or pip document is 12294 // destroyed, which might mean the PiP is already closing. 12295 if (bc->GetIsDocumentPiP() && !bc->GetClosed()) { 12296 if (IsUncommittedInitialDocument()) { 12297 // Don't close us if we're just doing the initial about:blank load. 12298 return; 12299 } 12300 return bc->Close(CallerType::System, IgnoreErrors()); 12301 } 12302 12303 // 4,5. Close a PIP window opened by us 12304 if (nsPIDOMWindowInner* inner = GetInnerWindow()) { 12305 if (DocumentPictureInPicture* dpip = 12306 inner->GetExtantDocumentPictureInPicture()) { 12307 if (RefPtr<nsGlobalWindowInner> pipWindow = dpip->GetWindow()) { 12308 pipWindow->Close(); 12309 } 12310 } 12311 } 12312 } 12313 12314 void Document::Destroy() { 12315 // The DocumentViewer wants to release the document now. So, tell our content 12316 // to drop any references to the document so that it can be destroyed. 12317 if (mIsGoingAway) { 12318 return; 12319 } 12320 12321 if (RefPtr transition = mActiveViewTransition) { 12322 transition->SkipTransition(SkipTransitionReason::DocumentHidden); 12323 } 12324 12325 RemoveCustomContentContainer(); 12326 12327 ReportDocumentUseCounters(); 12328 ReportShadowedProperties(); 12329 ReportLCP(); 12330 SetDevToolsWatchingDOMMutations(false); 12331 12332 mIsGoingAway = true; 12333 12334 if (mScriptLoader) { 12335 mScriptLoader->Destroy(); 12336 } 12337 SetScriptGlobalObject(nullptr); 12338 RemovedFromDocShell(); 12339 12340 bool oldVal = mInUnlinkOrDeletion; 12341 mInUnlinkOrDeletion = true; 12342 12343 #ifdef DEBUG 12344 uint32_t oldChildCount = GetChildCount(); 12345 #endif 12346 12347 for (nsIContent* child = GetFirstChild(); child; 12348 child = child->GetNextSibling()) { 12349 child->DestroyContent(); 12350 MOZ_ASSERT(child->GetParentNode() == this); 12351 } 12352 MOZ_ASSERT(oldChildCount == GetChildCount()); 12353 MOZ_ASSERT(!mSubDocuments || mSubDocuments->EntryCount() == 0); 12354 12355 mInUnlinkOrDeletion = oldVal; 12356 12357 mLayoutHistoryState = nullptr; 12358 12359 if (mOriginalDocument) { 12360 mOriginalDocument->mLatestStaticClone = nullptr; 12361 } 12362 12363 if (IsStaticDocument()) { 12364 RemoveProperty(nsGkAtoms::printisfocuseddoc); 12365 RemoveProperty(nsGkAtoms::printselectionranges); 12366 } 12367 12368 // Shut down our external resource map. We might not need this for 12369 // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but 12370 // tearing down all those frame trees right now is the right thing to do. 12371 mExternalResourceMap.Shutdown(); 12372 12373 // Manually break cycles via promise's global object pointer. 12374 mReadyForIdle = nullptr; 12375 mOrientationPendingPromise = nullptr; 12376 12377 // To break cycles. 12378 mPreloadService.ClearAllPreloads(); 12379 12380 if (mDocumentL10n) { 12381 mDocumentL10n->Destroy(); 12382 } 12383 12384 if (!mPresShell) { 12385 DropStyleSet(); 12386 } 12387 } 12388 12389 void Document::RemovedFromDocShell() { 12390 mEditingState = EditingState::eOff; 12391 12392 if (mRemovedFromDocShell) return; 12393 12394 mRemovedFromDocShell = true; 12395 NotifyActivityChanged(); 12396 12397 for (nsIContent* child = GetFirstChild(); child; 12398 child = child->GetNextSibling()) { 12399 child->SaveSubtreeState(); 12400 } 12401 12402 nsIDocShell* docShell = GetDocShell(); 12403 if (docShell) { 12404 docShell->SynchronizeLayoutHistoryState(); 12405 } 12406 } 12407 12408 already_AddRefed<nsILayoutHistoryState> Document::GetLayoutHistoryState() 12409 const { 12410 nsCOMPtr<nsILayoutHistoryState> state; 12411 if (!mScriptGlobalObject) { 12412 state = mLayoutHistoryState; 12413 } else { 12414 nsCOMPtr<nsIDocShell> docShell(mDocumentContainer); 12415 if (docShell) { 12416 docShell->GetLayoutHistoryState(getter_AddRefs(state)); 12417 } 12418 } 12419 12420 return state.forget(); 12421 } 12422 12423 void Document::EnsureOnloadBlocker() { 12424 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup 12425 // -- it's not ours. 12426 if (mOnloadBlockCount != 0 && mScriptGlobalObject) { 12427 nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup(); 12428 if (loadGroup) { 12429 // Check first to see if mOnloadBlocker is in the loadgroup. 12430 nsCOMPtr<nsISimpleEnumerator> requests; 12431 loadGroup->GetRequests(getter_AddRefs(requests)); 12432 12433 bool hasMore = false; 12434 while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) { 12435 nsCOMPtr<nsISupports> elem; 12436 requests->GetNext(getter_AddRefs(elem)); 12437 nsCOMPtr<nsIRequest> request = do_QueryInterface(elem); 12438 if (request && request == mOnloadBlocker) { 12439 return; 12440 } 12441 } 12442 12443 // Not in the loadgroup, so add it. 12444 loadGroup->AddRequest(mOnloadBlocker, nullptr); 12445 } 12446 } 12447 } 12448 12449 void Document::BlockOnload() { 12450 if (mDisplayDocument) { 12451 mDisplayDocument->BlockOnload(); 12452 return; 12453 } 12454 12455 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup 12456 // -- it's not ours. 12457 // If we're already complete there's no need to mess with the loadgroup 12458 // either, we're not blocking the load event after all. 12459 // Note that ready state is not reliable for the initial about:blank. 12460 if (mOnloadBlockCount == 0 && mScriptGlobalObject && 12461 (mReadyState != ReadyState::READYSTATE_COMPLETE || 12462 mInitialAboutBlankLoadCompleting)) { 12463 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) { 12464 loadGroup->AddRequest(mOnloadBlocker, nullptr); 12465 } 12466 } 12467 ++mOnloadBlockCount; 12468 } 12469 12470 void Document::UnblockOnload(bool aFireSync) { 12471 if (mDisplayDocument) { 12472 mDisplayDocument->UnblockOnload(aFireSync); 12473 return; 12474 } 12475 12476 --mOnloadBlockCount; 12477 12478 if (mOnloadBlockCount != 0) { 12479 return; 12480 } 12481 if (mScriptGlobalObject) { 12482 // Only manipulate the loadgroup in this case, because if 12483 // mScriptGlobalObject is null, it's not ours. 12484 if (aFireSync) { 12485 // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it 12486 ++mOnloadBlockCount; 12487 DoUnblockOnload(); 12488 } else { 12489 PostUnblockOnloadEvent(); 12490 } 12491 } else if (mIsBeingUsedAsImage) { 12492 // To correctly unblock onload for a document that contains an SVG 12493 // image, we need to know when all of the SVG document's resources are 12494 // done loading, in a way comparable to |window.onload|. We fire this 12495 // event to indicate that the SVG should be considered fully loaded. 12496 // Because scripting is disabled on SVG-as-image documents, this event 12497 // is not accessible to content authors. (See bug 837315.) 12498 RefPtr<AsyncEventDispatcher> asyncDispatcher = 12499 new AsyncEventDispatcher(this, u"MozSVGAsImageDocumentLoad"_ns, 12500 CanBubble::eNo, ChromeOnlyDispatch::eNo); 12501 asyncDispatcher->PostDOMEvent(); 12502 } 12503 } 12504 12505 class nsUnblockOnloadEvent : public Runnable { 12506 public: 12507 explicit nsUnblockOnloadEvent(Document* aDoc) 12508 : mozilla::Runnable("nsUnblockOnloadEvent"), mDoc(aDoc) {} 12509 NS_IMETHOD Run() override { 12510 mDoc->DoUnblockOnload(); 12511 return NS_OK; 12512 } 12513 12514 private: 12515 RefPtr<Document> mDoc; 12516 }; 12517 12518 void Document::PostUnblockOnloadEvent() { 12519 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 12520 nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this); 12521 nsresult rv = Dispatch(evt.forget()); 12522 if (NS_SUCCEEDED(rv)) { 12523 // Stabilize block count so we don't post more events while this one is up 12524 ++mOnloadBlockCount; 12525 } else { 12526 NS_WARNING("failed to dispatch nsUnblockOnloadEvent"); 12527 } 12528 } 12529 12530 void Document::DoUnblockOnload() { 12531 MOZ_ASSERT(!mDisplayDocument, "Shouldn't get here for resource document"); 12532 MOZ_ASSERT(mOnloadBlockCount != 0, 12533 "Shouldn't have a count of zero here, since we stabilized in " 12534 "PostUnblockOnloadEvent"); 12535 12536 --mOnloadBlockCount; 12537 12538 if (mOnloadBlockCount != 0) { 12539 // We blocked again after the last unblock. Nothing to do here. We'll 12540 // post a new event when we unblock again. 12541 return; 12542 } 12543 12544 // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup 12545 // -- it's not ours. 12546 if (mScriptGlobalObject) { 12547 if (nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup()) { 12548 loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK); 12549 } 12550 } 12551 } 12552 12553 nsIContent* Document::GetContentInThisDocument(nsIFrame* aFrame) const { 12554 for (nsIFrame* f = aFrame; f; 12555 f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { 12556 nsIContent* content = f->GetContent(); 12557 if (!content) { 12558 continue; 12559 } 12560 12561 if (content->OwnerDoc() == this) { 12562 return content; 12563 } 12564 // We must be in a subdocument so jump directly to the root frame. 12565 // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to 12566 // the containing document. 12567 f = f->PresContext()->GetPresShell()->GetRootFrame(); 12568 } 12569 12570 return nullptr; 12571 } 12572 12573 void Document::DispatchPageTransition(EventTarget* aDispatchTarget, 12574 const nsAString& aType, bool aInFrameSwap, 12575 bool aPersisted, bool aOnlySystemGroup) { 12576 if (!aDispatchTarget) { 12577 return; 12578 } 12579 12580 PageTransitionEventInit init; 12581 init.mBubbles = true; 12582 init.mCancelable = true; 12583 init.mPersisted = aPersisted; 12584 init.mInFrameSwap = aInFrameSwap; 12585 12586 RefPtr<PageTransitionEvent> event = 12587 PageTransitionEvent::Constructor(this, aType, init); 12588 12589 event->SetTrusted(true); 12590 event->SetTarget(this); 12591 if (aOnlySystemGroup) { 12592 event->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent = true; 12593 } 12594 EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr, 12595 nullptr); 12596 } 12597 12598 void Document::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget, 12599 bool aOnlySystemGroup) { 12600 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) { 12601 nsCString uri; 12602 if (GetDocumentURI()) { 12603 uri = GetDocumentURI()->GetSpecOrDefault(); 12604 } 12605 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, 12606 ("Document::OnPageShow [%s] persisted=%i", uri.get(), aPersisted)); 12607 } 12608 12609 const bool inFrameLoaderSwap = !!aDispatchStartTarget; 12610 MOZ_DIAGNOSTIC_ASSERT( 12611 inFrameLoaderSwap == 12612 (mDocumentContainer && mDocumentContainer->InFrameSwap())); 12613 12614 Element* root = GetRootElement(); 12615 if (aPersisted && root) { 12616 // Send out notifications that our <link> elements are attached. 12617 RefPtr<nsContentList> links = 12618 NS_GetContentList(root, kNameSpaceID_XHTML, u"link"_ns); 12619 12620 uint32_t linkCount = links->Length(true); 12621 for (uint32_t i = 0; i < linkCount; ++i) { 12622 static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded(); 12623 } 12624 } 12625 12626 // See Document 12627 if (!inFrameLoaderSwap) { 12628 if (aPersisted) { 12629 SetImageAnimationState(true); 12630 } 12631 12632 // Set mIsShowing before firing events, in case those event handlers 12633 // move us around. 12634 mIsShowing = true; 12635 mVisible = true; 12636 12637 UpdateVisibilityState(); 12638 } 12639 12640 NotifyActivityChanged(); 12641 12642 EnumerateExternalResources([aPersisted](Document& aExternalResource) { 12643 aExternalResource.OnPageShow(aPersisted, nullptr); 12644 return CallState::Continue; 12645 }); 12646 12647 if (mAnimationController) { 12648 mAnimationController->OnPageShow(); 12649 } 12650 12651 if (!mIsBeingUsedAsImage) { 12652 // Dispatch observer notification to notify observers page is shown. 12653 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 12654 if (os) { 12655 nsIPrincipal* principal = NodePrincipal(); 12656 os->NotifyObservers(ToSupports(this), 12657 principal->IsSystemPrincipal() ? "chrome-page-shown" 12658 : "content-page-shown", 12659 nullptr); 12660 } 12661 12662 nsCOMPtr<EventTarget> target = aDispatchStartTarget; 12663 if (!target) { 12664 target = do_QueryInterface(GetWindow()); 12665 } 12666 DispatchPageTransition(target, u"pageshow"_ns, inFrameLoaderSwap, 12667 aPersisted, aOnlySystemGroup); 12668 } 12669 12670 if (auto* wgc = GetWindowGlobalChild()) { 12671 wgc->UnblockBFCacheFor(BFCacheStatus::PAGE_LOADING); 12672 } 12673 12674 mIsCompletelyLoaded = true; 12675 } 12676 12677 static void DispatchFullscreenChange(Document& aDocument, nsINode* aTarget) { 12678 aDocument.AddPendingFullscreenEvent( 12679 MakeUnique<PendingFullscreenEvent>(FullscreenEventType::Change, aTarget)); 12680 } 12681 12682 void Document::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget, 12683 bool aOnlySystemGroup) { 12684 if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) { 12685 nsCString uri; 12686 if (GetDocumentURI()) { 12687 uri = GetDocumentURI()->GetSpecOrDefault(); 12688 } 12689 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, 12690 ("Document::OnPageHide %s persisted=%i", uri.get(), aPersisted)); 12691 } 12692 12693 const bool inFrameLoaderSwap = !!aDispatchStartTarget; 12694 MOZ_DIAGNOSTIC_ASSERT( 12695 inFrameLoaderSwap == 12696 (mDocumentContainer && mDocumentContainer->InFrameSwap())); 12697 12698 if (mAnimationController) { 12699 mAnimationController->OnPageHide(); 12700 } 12701 12702 if (inFrameLoaderSwap) { 12703 if (RefPtr transition = mActiveViewTransition) { 12704 transition->SkipTransition(SkipTransitionReason::PageSwap); 12705 } 12706 } else { 12707 if (aPersisted) { 12708 // We do not stop the animations (bug 1024343) when the page is refreshing 12709 // while being dragged out. 12710 SetImageAnimationState(false); 12711 } 12712 12713 // Set mIsShowing before firing events, in case those event handlers 12714 // move us around. 12715 mIsShowing = false; 12716 mVisible = false; 12717 } 12718 12719 // https://wicg.github.io/document-picture-in-picture/#close-on-destroy 12720 CloseAnyAssociatedDocumentPiPWindows(); 12721 12722 PointerLockManager::Unlock("Document::OnPageHide", this); 12723 12724 if (!mIsBeingUsedAsImage) { 12725 // Dispatch observer notification to notify observers page is hidden. 12726 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 12727 if (os) { 12728 nsIPrincipal* principal = NodePrincipal(); 12729 os->NotifyObservers(ToSupports(this), 12730 principal->IsSystemPrincipal() 12731 ? "chrome-page-hidden" 12732 : "content-page-hidden", 12733 nullptr); 12734 } 12735 12736 // Now send out a PageHide event. 12737 nsCOMPtr<EventTarget> target = aDispatchStartTarget; 12738 if (!target) { 12739 target = do_QueryInterface(GetWindow()); 12740 } 12741 { 12742 PageUnloadingEventTimeStamp timeStamp(this); 12743 DispatchPageTransition(target, u"pagehide"_ns, inFrameLoaderSwap, 12744 aPersisted, aOnlySystemGroup); 12745 } 12746 } 12747 12748 if (!inFrameLoaderSwap) { 12749 UpdateVisibilityState(); 12750 } 12751 12752 EnumerateExternalResources([aPersisted](Document& aExternalResource) { 12753 aExternalResource.OnPageHide(aPersisted, nullptr); 12754 return CallState::Continue; 12755 }); 12756 NotifyActivityChanged(); 12757 12758 ClearPendingFullscreenRequests(this); 12759 if (Fullscreen()) { 12760 // If this document was fullscreen, we should exit fullscreen in this 12761 // doctree branch. This ensures that if the user navigates while in 12762 // fullscreen mode we don't leave its still visible ancestor documents 12763 // in fullscreen mode. So exit fullscreen in the document's fullscreen 12764 // root document, as this will exit fullscreen in all the root's 12765 // descendant documents. Note that documents are removed from the 12766 // doctree by the time OnPageHide() is called, so we must store a 12767 // reference to the root (in Document::mFullscreenRoot) since we can't 12768 // just traverse the doctree to get the root. 12769 Document::ExitFullscreenInDocTree(this); 12770 12771 // Since the document is removed from the doctree before OnPageHide() is 12772 // called, ExitFullscreen() can't traverse from the root down to *this* 12773 // document, so we must manually call CleanupFullscreenState() below too. 12774 // Note that CleanupFullscreenState() clears Document::mFullscreenRoot, 12775 // so we *must* call it after ExitFullscreen(), not before. 12776 // OnPageHide() is called in every hidden (i.e. descendant) document, 12777 // so calling CleanupFullscreenState() here will ensure all hidden 12778 // documents have their fullscreen state reset. 12779 CleanupFullscreenState(); 12780 12781 // The fullscreenchange event is to be queued in the refresh driver, 12782 // however a hidden page wouldn't trigger that again, so it makes no 12783 // sense to dispatch such event here. 12784 } 12785 } 12786 12787 void Document::WillRemoveRoot() { 12788 #ifdef DEBUG 12789 mStyledLinksCleared = true; 12790 #endif 12791 mStyledLinks.Clear(); 12792 // Notify ID change listeners before clearing the identifier map. 12793 for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) { 12794 iter.Get()->ClearAndNotify(); 12795 } 12796 mIdentifierMap.Clear(); 12797 mComposedShadowRoots.Clear(); 12798 mResponsiveContent.Clear(); 12799 12800 // Skip any active view transition, since the view transition pseudo-element 12801 // tree is attached to our root element. This is not in the spec (yet), but 12802 // prevents the view transition pseudo tree from being in an inconsistent 12803 // state. See https://github.com/w3c/csswg-drafts/issues/12149 12804 if (RefPtr transition = mActiveViewTransition) { 12805 transition->SkipTransition(SkipTransitionReason::RootRemoved); 12806 } 12807 12808 RemoveCustomContentContainer(); 12809 IncrementExpandoGeneration(*this); 12810 } 12811 12812 void Document::RefreshLinkHrefs() { 12813 // Get a list of all links we know about. We will reset them, which will 12814 // remove them from the document, so we need a copy of what is in the 12815 // hashtable. 12816 const nsTArray<Link*> linksToNotify = ToArray(mStyledLinks); 12817 12818 // Reset all of our styled links. 12819 nsAutoScriptBlocker scriptBlocker; 12820 for (Link* link : linksToNotify) { 12821 link->ResetLinkState(true); 12822 } 12823 } 12824 12825 nsresult Document::CloneDocHelper(Document* clone) const { 12826 clone->mIsStaticDocument = mCreatingStaticClone; 12827 12828 // Init document 12829 nsresult rv = clone->Init(NodePrincipal(), mPartitionedPrincipal); 12830 NS_ENSURE_SUCCESS(rv, rv); 12831 12832 if (mCreatingStaticClone) { 12833 if (mOriginalDocument) { 12834 clone->mOriginalDocument = mOriginalDocument; 12835 } else { 12836 clone->mOriginalDocument = const_cast<Document*>(this); 12837 } 12838 clone->mOriginalDocument->mLatestStaticClone = clone; 12839 clone->mOriginalDocument->mStaticCloneCount++; 12840 12841 nsCOMPtr<nsILoadGroup> loadGroup; 12842 12843 // |mDocumentContainer| is the container of the document that is being 12844 // created and not the original container. See CreateStaticClone function(). 12845 nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer); 12846 if (docLoader) { 12847 docLoader->GetLoadGroup(getter_AddRefs(loadGroup)); 12848 } 12849 nsCOMPtr<nsIChannel> channel = GetChannel(); 12850 nsCOMPtr<nsIURI> uri; 12851 if (channel) { 12852 NS_GetFinalChannelURI(channel, getter_AddRefs(uri)); 12853 } else { 12854 uri = Document::GetDocumentURI(); 12855 } 12856 clone->mChannel = channel; 12857 clone->mShouldResistFingerprinting = mShouldResistFingerprinting; 12858 if (uri) { 12859 clone->ResetToURI(uri, loadGroup, NodePrincipal(), mPartitionedPrincipal); 12860 } 12861 12862 clone->mIsSrcdocDocument = mIsSrcdocDocument; 12863 clone->SetContainer(mDocumentContainer); 12864 12865 // Setup the navigation time. This will be needed by any animations in the 12866 // document, even if they are only paused. 12867 MOZ_ASSERT(!clone->GetNavigationTiming(), 12868 "Navigation time was already set?"); 12869 if (mTiming) { 12870 RefPtr<nsDOMNavigationTiming> timing = 12871 mTiming->CloneNavigationTime(nsDocShell::Cast(clone->GetDocShell())); 12872 clone->SetNavigationTiming(timing); 12873 } 12874 clone->SetPolicyContainer(mPolicyContainer); 12875 } 12876 12877 // Now ensure that our clone has the same URI, base URI, and principal as us. 12878 // We do this after the mCreatingStaticClone block above, because that block 12879 // can set the base URI to an incorrect value in cases when base URI 12880 // information came from the channel. So we override explicitly, and do it 12881 // for all these properties, in case ResetToURI messes with any of the rest of 12882 // them. 12883 clone->SetDocumentURI(Document::GetDocumentURI()); 12884 clone->SetChromeXHRDocURI(mChromeXHRDocURI); 12885 clone->mActiveStoragePrincipal = mActiveStoragePrincipal; 12886 clone->mActiveCookiePrincipal = mActiveCookiePrincipal; 12887 // NOTE(emilio): Intentionally setting this to the GetDocBaseURI rather than 12888 // just mDocumentBaseURI, so that srcdoc iframes get the right base URI even 12889 // when printed standalone via window.print() (where there won't be a parent 12890 // document to grab the URI from). 12891 clone->mDocumentBaseURI = GetDocBaseURI(); 12892 clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI); 12893 clone->mReferrerInfo = 12894 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone(); 12895 clone->mPreloadReferrerInfo = clone->mReferrerInfo; 12896 12897 bool hasHadScriptObject = true; 12898 nsIScriptGlobalObject* scriptObject = 12899 GetScriptHandlingObject(hasHadScriptObject); 12900 NS_ENSURE_STATE(scriptObject || !hasHadScriptObject); 12901 if (mCreatingStaticClone) { 12902 // If we're doing a static clone (print, print preview), then we're going to 12903 // be setting a scope object after the clone. It's better to set it only 12904 // once, so we don't do that here. However, we do want to act as if there is 12905 // a script handling object. So we set mHasHadScriptHandlingObject. 12906 clone->mHasHadScriptHandlingObject = true; 12907 } else if (scriptObject) { 12908 clone->SetScriptHandlingObject(scriptObject); 12909 } else { 12910 clone->SetScopeObject(GetScopeObject()); 12911 } 12912 // Make the clone a data document 12913 clone->SetLoadedAsData( 12914 true, 12915 /* aConsiderForMemoryReporting */ !mCreatingStaticClone); 12916 12917 // Misc state 12918 12919 // State from Document 12920 clone->mCharacterSet = mCharacterSet; 12921 clone->mCharacterSetSource = mCharacterSetSource; 12922 clone->SetCompatibilityMode(mCompatMode); 12923 clone->mBidiOptions = mBidiOptions; 12924 clone->mContentLanguage = mContentLanguage; 12925 clone->SetContentType(GetContentTypeInternal()); 12926 clone->mSecurityInfo = mSecurityInfo; 12927 12928 // State from Document 12929 clone->mType = mType; 12930 clone->mXMLDeclarationBits = mXMLDeclarationBits; 12931 clone->mBaseTarget = mBaseTarget; 12932 clone->mAllowDeclarativeShadowRoots = mAllowDeclarativeShadowRoots; 12933 12934 return NS_OK; 12935 } 12936 12937 void Document::NotifyLoading(bool aNewParentIsLoading, 12938 const ReadyState& aCurrentState, 12939 ReadyState aNewState) { 12940 // Mirror the top-level loading state down to all subdocuments 12941 bool was_loading = mAncestorIsLoading || 12942 aCurrentState == READYSTATE_LOADING || 12943 aCurrentState == READYSTATE_INTERACTIVE; 12944 bool is_loading = aNewParentIsLoading || aNewState == READYSTATE_LOADING || 12945 aNewState == READYSTATE_INTERACTIVE; // new value for state 12946 bool set_load_state = was_loading != is_loading; 12947 12948 MOZ_LOG( 12949 gTimeoutDeferralLog, mozilla::LogLevel::Debug, 12950 ("NotifyLoading for doc %p: currentAncestor: %d, newParent: %d, " 12951 "currentState %d newState: %d, was_loading: %d, is_loading: %d, " 12952 "set_load_state: %d", 12953 (void*)this, mAncestorIsLoading, aNewParentIsLoading, (int)aCurrentState, 12954 (int)aNewState, was_loading, is_loading, set_load_state)); 12955 12956 mAncestorIsLoading = aNewParentIsLoading; 12957 if (set_load_state && StaticPrefs::dom_timeout_defer_during_load() && 12958 !NodePrincipal()->IsURIInPrefList( 12959 "dom.timeout.defer_during_load.force-disable")) { 12960 // Tell our innerwindow (and thus TimeoutManager) 12961 nsPIDOMWindowInner* inner = GetInnerWindow(); 12962 if (inner) { 12963 inner->SetActiveLoadingState(is_loading); 12964 } 12965 BrowsingContext* context = GetBrowsingContext(); 12966 if (context) { 12967 // Don't use PreOrderWalk to mirror this down; go down one level as a 12968 // time so we can set mAncestorIsLoading and take into account the 12969 // readystates of the subdocument. In the child process it will call 12970 // NotifyLoading() to notify the innerwindow/TimeoutManager, and then 12971 // iterate it's children 12972 for (auto& child : context->Children()) { 12973 MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug, 12974 ("bc: %p SetAncestorLoading(%d)", (void*)child, is_loading)); 12975 // Setting ancestor loading on a discarded browsing context has no 12976 // effect. 12977 (void)child->SetAncestorLoading(is_loading); 12978 } 12979 } 12980 } 12981 } 12982 12983 void Document::SetReadyStateInternal(ReadyState aReadyState, 12984 bool aUpdateTimingInformation) { 12985 if (aReadyState == READYSTATE_UNINITIALIZED) { 12986 // Transition back to uninitialized happens only to keep assertions happy 12987 // right before readyState transitions to something else. Make this 12988 // transition undetectable by Web content. 12989 mReadyState = aReadyState; 12990 return; 12991 } 12992 12993 if (IsTopLevelContentDocument()) { 12994 if (aReadyState == READYSTATE_LOADING) { 12995 AddToplevelLoadingDocument(this); 12996 } else if (aReadyState == READYSTATE_COMPLETE) { 12997 RemoveToplevelLoadingDocument(this); 12998 } 12999 } 13000 13001 if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) { 13002 SetLoadingOrRestoredFromBFCacheTimeStampToNow(); 13003 } 13004 NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState); 13005 mReadyState = aReadyState; 13006 if (aUpdateTimingInformation && mTiming) { 13007 switch (aReadyState) { 13008 case READYSTATE_LOADING: 13009 mTiming->NotifyDOMLoading(GetDocumentURI()); 13010 break; 13011 case READYSTATE_INTERACTIVE: 13012 mTiming->NotifyDOMInteractive(GetDocumentURI()); 13013 break; 13014 case READYSTATE_COMPLETE: 13015 mTiming->NotifyDOMComplete(GetDocumentURI()); 13016 break; 13017 default: 13018 MOZ_ASSERT_UNREACHABLE("Unexpected ReadyState value"); 13019 break; 13020 } 13021 } 13022 // At the time of loading start, we don't have timing object, record time. 13023 13024 if (READYSTATE_INTERACTIVE == aReadyState && 13025 NodePrincipal()->IsSystemPrincipal()) { 13026 if (!mXULPersist && XRE_IsParentProcess()) { 13027 mXULPersist = new XULPersist(this); 13028 mXULPersist->Init(); 13029 } 13030 if (!mChromeObserver) { 13031 mChromeObserver = new ChromeObserver(this); 13032 mChromeObserver->Init(); 13033 } 13034 } 13035 13036 if (aUpdateTimingInformation) { 13037 RecordNavigationTiming(aReadyState); 13038 } 13039 13040 AsyncEventDispatcher::RunDOMEventWhenSafe( 13041 *this, u"readystatechange"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo); 13042 } 13043 13044 void Document::GetReadyState(nsAString& aReadyState) const { 13045 switch (mReadyState) { 13046 case READYSTATE_LOADING: 13047 aReadyState.AssignLiteral(u"loading"); 13048 break; 13049 case READYSTATE_INTERACTIVE: 13050 aReadyState.AssignLiteral(u"interactive"); 13051 break; 13052 case READYSTATE_COMPLETE: 13053 aReadyState.AssignLiteral(u"complete"); 13054 break; 13055 default: 13056 aReadyState.AssignLiteral(u"uninitialized"); 13057 } 13058 } 13059 13060 void Document::SuppressEventHandling(uint32_t aIncrease) { 13061 mEventsSuppressed += aIncrease; 13062 if (mEventsSuppressed == aIncrease) { 13063 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) { 13064 wgc->BlockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED); 13065 } 13066 } 13067 if (mScriptLoader) { 13068 for (uint32_t i = 0; i < aIncrease; ++i) { 13069 mScriptLoader->AddExecuteBlocker(); 13070 } 13071 } 13072 13073 EnumerateSubDocuments([aIncrease](Document& aSubDoc) { 13074 aSubDoc.SuppressEventHandling(aIncrease); 13075 return CallState::Continue; 13076 }); 13077 } 13078 13079 void Document::NotifyAbortedLoad() { 13080 // If we still have outstanding work blocking DOMContentLoaded, 13081 // then don't try to change the readystate now, but wait until 13082 // they finish and then do so. 13083 if (mBlockDOMContentLoaded > 0 && !mDidFireDOMContentLoaded) { 13084 mSetCompleteAfterDOMContentLoaded = true; 13085 return; 13086 } 13087 13088 // Otherwise we're fully done at this point, so set the 13089 // readystate to complete. 13090 if (GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE) { 13091 SetReadyStateInternal(Document::READYSTATE_COMPLETE); 13092 } 13093 } 13094 13095 MOZ_CAN_RUN_SCRIPT static void FireOrClearDelayedEvents( 13096 nsTArray<nsCOMPtr<Document>>&& aDocuments, bool aFireEvents) { 13097 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager(); 13098 if (MOZ_UNLIKELY(!fm)) { 13099 return; 13100 } 13101 13102 nsTArray<nsCOMPtr<Document>> documents = std::move(aDocuments); 13103 for (uint32_t i = 0; i < documents.Length(); ++i) { 13104 nsCOMPtr<Document> document = std::move(documents[i]); 13105 // NB: Don't bother trying to fire delayed events on documents that were 13106 // closed before this event ran. 13107 if (!document->EventHandlingSuppressed()) { 13108 fm->FireDelayedEvents(document); 13109 RefPtr<PresShell> presShell = document->GetPresShell(); 13110 if (presShell) { 13111 // Only fire events for active documents. 13112 bool fire = aFireEvents && document->GetInnerWindow() && 13113 document->GetInnerWindow()->IsCurrentInnerWindow(); 13114 presShell->FireOrClearDelayedEvents(fire); 13115 } 13116 document->FireOrClearPostMessageEvents(aFireEvents); 13117 } 13118 } 13119 } 13120 13121 void Document::PreloadPictureClosed() { 13122 MOZ_ASSERT(mPreloadPictureDepth > 0); 13123 mPreloadPictureDepth--; 13124 if (mPreloadPictureDepth == 0) { 13125 mPreloadPictureFoundSource.SetIsVoid(true); 13126 } 13127 } 13128 13129 void Document::PreloadPictureImageSource(const nsAString& aSrcsetAttr, 13130 const nsAString& aSizesAttr, 13131 const nsAString& aTypeAttr, 13132 const nsAString& aMediaAttr) { 13133 // Nested pictures are not valid syntax, so while we'll eventually load them, 13134 // it's not worth tracking sources mixed between nesting levels to preload 13135 // them effectively. 13136 if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) { 13137 // <picture> selects the first matching source, so if this returns a URI we 13138 // needn't consider new sources until a new <picture> is encountered. 13139 bool found = HTMLImageElement::SelectSourceForTagWithAttrs( 13140 this, true, VoidString(), aSrcsetAttr, aSizesAttr, aTypeAttr, 13141 aMediaAttr, mPreloadPictureFoundSource); 13142 if (found && mPreloadPictureFoundSource.IsVoid()) { 13143 // Found an empty source, which counts 13144 mPreloadPictureFoundSource.SetIsVoid(false); 13145 } 13146 } 13147 } 13148 13149 already_AddRefed<nsIURI> Document::ResolvePreloadImage( 13150 nsIURI* aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr, 13151 const nsAString& aSizesAttr, bool* aIsImgSet) { 13152 nsString sourceURL; 13153 bool isImgSet; 13154 if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) { 13155 // We're in a <picture> element and found a URI from a source previous to 13156 // this image, use it. 13157 sourceURL = mPreloadPictureFoundSource; 13158 isImgSet = true; 13159 } else { 13160 // Otherwise try to use this <img> as a source 13161 HTMLImageElement::SelectSourceForTagWithAttrs( 13162 this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, VoidString(), 13163 VoidString(), sourceURL); 13164 isImgSet = !aSrcsetAttr.IsEmpty(); 13165 } 13166 13167 // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI) 13168 if (sourceURL.IsEmpty()) { 13169 return nullptr; 13170 } 13171 13172 // Construct into URI using passed baseURI (the parser may know of base URI 13173 // changes that have not reached us) 13174 nsresult rv; 13175 nsCOMPtr<nsIURI> uri; 13176 rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL, 13177 this, aBaseURI); 13178 if (NS_FAILED(rv)) { 13179 return nullptr; 13180 } 13181 13182 *aIsImgSet = isImgSet; 13183 13184 // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in 13185 // this this <picture> share the same <sources> (though this is not valid per 13186 // spec) 13187 return uri.forget(); 13188 } 13189 13190 void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr, 13191 ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet, 13192 bool aLinkPreload, uint64_t aEarlyHintPreloaderId, 13193 const nsAString& aFetchPriority) { 13194 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | 13195 nsContentUtils::CORSModeToLoadImageFlags( 13196 Element::StringToCORSMode(aCrossOriginAttr)); 13197 13198 nsContentPolicyType policyType = 13199 aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET 13200 : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD; 13201 13202 nsCOMPtr<nsIReferrerInfo> referrerInfo = 13203 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy); 13204 13205 RefPtr<imgRequestProxy> request; 13206 13207 nsLiteralString initiator = aEarlyHintPreloaderId 13208 ? u"early-hints"_ns 13209 : (aLinkPreload ? u"link"_ns : u"img"_ns); 13210 13211 nsresult rv = nsContentUtils::LoadImage( 13212 aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo, 13213 nullptr /* no observer */, loadFlags, initiator, getter_AddRefs(request), 13214 policyType, false /* urgent */, aLinkPreload, aEarlyHintPreloaderId, 13215 nsGenericHTMLElement::ToFetchPriority(aFetchPriority)); 13216 13217 // Pin image-reference to avoid evicting it from the img-cache before 13218 // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and 13219 // unlink 13220 if (!aLinkPreload && NS_SUCCEEDED(rv)) { 13221 mPreloadingImages.InsertOrUpdate(aUri, std::move(request)); 13222 } 13223 } 13224 13225 void Document::MaybePreLoadImage(nsIURI* aUri, 13226 const nsAString& aCrossOriginAttr, 13227 ReferrerPolicyEnum aReferrerPolicy, 13228 bool aIsImgSet, bool aLinkPreload, 13229 const nsAString& aFetchPriority) { 13230 const CORSMode corsMode = dom::Element::StringToCORSMode(aCrossOriginAttr); 13231 if (aLinkPreload) { 13232 // Check if the image was already preloaded in this document to avoid 13233 // duplicate preloading. 13234 PreloadHashKey key = 13235 PreloadHashKey::CreateAsImage(aUri, NodePrincipal(), corsMode); 13236 if (!mPreloadService.PreloadExists(key)) { 13237 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, 13238 aLinkPreload, 0, aFetchPriority); 13239 } 13240 return; 13241 } 13242 13243 // Early exit if the img is already present in the img-cache 13244 // which indicates that the "real" load has already started and 13245 // that we shouldn't preload it. 13246 if (nsContentUtils::IsImageAvailable(aUri, NodePrincipal(), corsMode, this)) { 13247 return; 13248 } 13249 13250 // Image not in cache - trigger preload 13251 PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet, aLinkPreload, 13252 0, aFetchPriority); 13253 } 13254 13255 void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) { 13256 if (!StaticPrefs::network_preconnect()) { 13257 return; 13258 } 13259 13260 NS_MutateURI mutator(aOrigURI); 13261 if (NS_FAILED(mutator.GetStatus())) { 13262 return; 13263 } 13264 13265 // The URI created here is used in 2 contexts. One is nsISpeculativeConnect 13266 // which ignores the path and uses only the origin. The other is for the 13267 // document mPreloadedPreconnects de-duplication hash. Anonymous vs 13268 // non-Anonymous preconnects create different connections on the wire and 13269 // therefore should not be considred duplicates of each other and we 13270 // normalize the path before putting it in the hash to accomplish that. 13271 13272 if (aCORSMode == CORS_ANONYMOUS) { 13273 mutator.SetPathQueryRef("/anonymous"_ns); 13274 } else { 13275 mutator.SetPathQueryRef("/"_ns); 13276 } 13277 13278 nsCOMPtr<nsIURI> uri; 13279 nsresult rv = mutator.Finalize(uri); 13280 if (NS_FAILED(rv)) { 13281 return; 13282 } 13283 13284 const bool existingEntryFound = 13285 mPreloadedPreconnects.WithEntryHandle(uri, [](auto&& entry) { 13286 if (entry) { 13287 return true; 13288 } 13289 entry.Insert(true); 13290 return false; 13291 }); 13292 if (existingEntryFound) { 13293 return; 13294 } 13295 13296 nsCOMPtr<nsISpeculativeConnect> speculator = 13297 mozilla::components::IO::Service(); 13298 if (!speculator) { 13299 return; 13300 } 13301 13302 OriginAttributes oa; 13303 StoragePrincipalHelper::GetOriginAttributesForNetworkState(this, oa); 13304 speculator->SpeculativeConnectWithOriginAttributesNative( 13305 uri, std::move(oa), nullptr, aCORSMode == CORS_ANONYMOUS); 13306 } 13307 13308 void Document::ForgetImagePreload(nsIURI* aURI) { 13309 // Checking count is faster than hashing the URI in the common 13310 // case of empty table. 13311 if (mPreloadingImages.Count() != 0) { 13312 nsCOMPtr<imgIRequest> req; 13313 mPreloadingImages.Remove(aURI, getter_AddRefs(req)); 13314 if (req) { 13315 // Make sure to cancel the request so imagelib knows it's gone. 13316 req->CancelAndForgetObserver(NS_BINDING_ABORTED); 13317 } 13318 } 13319 } 13320 13321 void Document::UpdateDocumentStates(DocumentState aMaybeChangedStates, 13322 bool aNotify) { 13323 const DocumentState oldStates = mState; 13324 if (aMaybeChangedStates.HasAtLeastOneOfStates( 13325 DocumentState::ALL_LOCALEDIR_BITS)) { 13326 mState &= ~DocumentState::ALL_LOCALEDIR_BITS; 13327 if (IsDocumentRightToLeft()) { 13328 mState |= DocumentState::RTL_LOCALE; 13329 } else { 13330 mState |= DocumentState::LTR_LOCALE; 13331 } 13332 } 13333 13334 if (aMaybeChangedStates.HasState(DocumentState::WINDOW_INACTIVE)) { 13335 BrowsingContext* bc = GetBrowsingContext(); 13336 if (!bc || !bc->GetIsActiveBrowserWindow()) { 13337 mState |= DocumentState::WINDOW_INACTIVE; 13338 } else { 13339 mState &= ~DocumentState::WINDOW_INACTIVE; 13340 } 13341 } 13342 13343 const DocumentState changedStates = oldStates ^ mState; 13344 if (aNotify && !changedStates.IsEmpty()) { 13345 if (PresShell* ps = GetObservingPresShell()) { 13346 ps->DocumentStatesChanged(changedStates); 13347 } 13348 } 13349 } 13350 13351 namespace { 13352 13353 /** 13354 * Stub for LoadSheet(), since all we want is to get the sheet into 13355 * the CSSLoader's style cache 13356 */ 13357 class StubCSSLoaderObserver final : public nsICSSLoaderObserver { 13358 ~StubCSSLoaderObserver() = default; 13359 13360 public: 13361 NS_IMETHOD 13362 StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; } 13363 NS_DECL_ISUPPORTS 13364 }; 13365 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver) 13366 13367 } // namespace 13368 13369 SheetPreloadStatus Document::PreloadStyle( 13370 nsIURI* uri, const Encoding* aEncoding, const nsAString& aCrossOriginAttr, 13371 const enum ReferrerPolicy aReferrerPolicy, const nsAString& aNonce, 13372 const nsAString& aIntegrity, css::StylePreloadKind aKind, 13373 uint64_t aEarlyHintPreloaderId, const nsAString& aFetchPriority) { 13374 MOZ_ASSERT(aKind != css::StylePreloadKind::None); 13375 if (!mCSSLoader) { 13376 return SheetPreloadStatus::Errored; 13377 } 13378 13379 // The CSSLoader will retain this object after we return. 13380 nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver(); 13381 13382 nsCOMPtr<nsIReferrerInfo> referrerInfo = 13383 ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy); 13384 13385 // Charset names are always ASCII. 13386 auto result = mCSSLoader->LoadSheet( 13387 uri, aKind, aEncoding, referrerInfo, obs, aEarlyHintPreloaderId, 13388 Element::StringToCORSMode(aCrossOriginAttr), aNonce, aIntegrity, 13389 nsGenericHTMLElement::ToFetchPriority(aFetchPriority)); 13390 if (result.isErr()) { 13391 return SheetPreloadStatus::Errored; 13392 } 13393 RefPtr<StyleSheet> sheet = result.unwrap(); 13394 if (sheet->IsComplete()) { 13395 return SheetPreloadStatus::AlreadyComplete; 13396 } 13397 return SheetPreloadStatus::InProgress; 13398 } 13399 13400 void Document::ResetDocumentDirection() { 13401 if (!nsContentUtils::IsChromeDoc(this)) { 13402 return; 13403 } 13404 UpdateDocumentStates(DocumentState::ALL_LOCALEDIR_BITS, true); 13405 } 13406 13407 bool Document::IsDocumentRightToLeft() { 13408 if (!nsContentUtils::IsChromeDoc(this)) { 13409 return false; 13410 } 13411 // setting the localedir attribute on the root element forces a 13412 // specific direction for the document. 13413 Element* element = GetRootElement(); 13414 if (element) { 13415 static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr, nsGkAtoms::rtl, 13416 nullptr}; 13417 switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir, 13418 strings, eCaseMatters)) { 13419 case 0: 13420 return false; 13421 case 1: 13422 return true; 13423 default: 13424 break; // otherwise, not a valid value, so fall through 13425 } 13426 } 13427 13428 if (!mDocumentURI->SchemeIs("chrome") && !mDocumentURI->SchemeIs("about") && 13429 !mDocumentURI->SchemeIs("resource")) { 13430 return false; 13431 } 13432 13433 return intl::LocaleService::GetInstance()->IsAppLocaleRTL(); 13434 } 13435 13436 class nsDelayedEventDispatcher : public Runnable { 13437 public: 13438 explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<Document>>&& aDocuments) 13439 : mozilla::Runnable("nsDelayedEventDispatcher"), 13440 mDocuments(std::move(aDocuments)) {} 13441 virtual ~nsDelayedEventDispatcher() = default; 13442 13443 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See 13444 // bug 1535398. 13445 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override { 13446 FireOrClearDelayedEvents(std::move(mDocuments), true); 13447 return NS_OK; 13448 } 13449 13450 private: 13451 nsTArray<nsCOMPtr<Document>> mDocuments; 13452 }; 13453 13454 static void GetAndUnsuppressSubDocuments( 13455 Document& aDocument, nsTArray<nsCOMPtr<Document>>& aDocuments) { 13456 if (aDocument.EventHandlingSuppressed() > 0) { 13457 aDocument.DecreaseEventSuppression(); 13458 if (dom::ScriptLoader* loader = aDocument.GetScriptLoader()) { 13459 loader->RemoveExecuteBlocker(); 13460 } 13461 } 13462 aDocuments.AppendElement(&aDocument); 13463 aDocument.EnumerateSubDocuments([&aDocuments](Document& aSubDoc) { 13464 GetAndUnsuppressSubDocuments(aSubDoc, aDocuments); 13465 return CallState::Continue; 13466 }); 13467 } 13468 13469 void Document::UnsuppressEventHandlingAndFireEvents(bool aFireEvents) { 13470 nsTArray<nsCOMPtr<Document>> documents; 13471 GetAndUnsuppressSubDocuments(*this, documents); 13472 13473 for (nsCOMPtr<Document>& doc : documents) { 13474 if (!doc->EventHandlingSuppressed()) { 13475 if (WindowGlobalChild* wgc = doc->GetWindowGlobalChild()) { 13476 wgc->UnblockBFCacheFor(BFCacheStatus::EVENT_HANDLING_SUPPRESSED); 13477 } 13478 13479 MOZ_ASSERT(NS_IsMainThread()); 13480 nsTArray<RefPtr<net::ChannelEventQueue>> queues = 13481 std::move(doc->mSuspendedQueues); 13482 for (net::ChannelEventQueue* queue : queues) { 13483 queue->Resume(); 13484 } 13485 } 13486 } 13487 13488 if (aFireEvents) { 13489 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 13490 nsCOMPtr<nsIRunnable> ded = 13491 new nsDelayedEventDispatcher(std::move(documents)); 13492 Dispatch(ded.forget()); 13493 } else { 13494 FireOrClearDelayedEvents(std::move(documents), false); 13495 } 13496 } 13497 13498 bool Document::AreClipboardCommandsUnconditionallyEnabled() const { 13499 return IsHTMLOrXHTML() && !nsContentUtils::IsChromeDoc(this); 13500 } 13501 13502 void Document::AddSuspendedChannelEventQueue(net::ChannelEventQueue* aQueue) { 13503 MOZ_ASSERT(NS_IsMainThread()); 13504 MOZ_ASSERT(EventHandlingSuppressed()); 13505 mSuspendedQueues.AppendElement(aQueue); 13506 } 13507 13508 bool Document::SuspendPostMessageEvent(PostMessageEvent* aEvent) { 13509 MOZ_ASSERT(NS_IsMainThread()); 13510 13511 if (EventHandlingSuppressed() || !mSuspendedPostMessageEvents.IsEmpty()) { 13512 mSuspendedPostMessageEvents.AppendElement(aEvent); 13513 return true; 13514 } 13515 return false; 13516 } 13517 13518 void Document::FireOrClearPostMessageEvents(bool aFireEvents) { 13519 nsTArray<RefPtr<PostMessageEvent>> events = 13520 std::move(mSuspendedPostMessageEvents); 13521 13522 if (aFireEvents) { 13523 for (PostMessageEvent* event : events) { 13524 event->Run(); 13525 } 13526 } 13527 } 13528 13529 void Document::SetSuppressedEventListener(EventListener* aListener) { 13530 mSuppressedEventListener = aListener; 13531 EnumerateSubDocuments([&](Document& aDocument) { 13532 aDocument.SetSuppressedEventListener(aListener); 13533 return CallState::Continue; 13534 }); 13535 } 13536 13537 bool Document::IsActive() const { 13538 return mDocumentContainer && !mRemovedFromDocShell && GetBrowsingContext() && 13539 !GetBrowsingContext()->IsInBFCache(); 13540 } 13541 13542 uint32_t Document::LastScrollGeneration() const { 13543 if (nsPresContext* pc = GetPresContext()) { 13544 pc->LastScrollGeneration(); 13545 } 13546 13547 return 0; 13548 } 13549 13550 bool Document::HasBeenScrolledSince( 13551 const uint32_t& aLastScrollGeneration) const { 13552 if (nsPresContext* pc = GetPresContext()) { 13553 pc->HasBeenScrolledSince(aLastScrollGeneration); 13554 } 13555 13556 return false; 13557 } 13558 13559 bool Document::CanRewriteURL(nsIURI* aTargetURL, bool aReportErrors) const { 13560 if (nsContentUtils::URIIsLocalFile(aTargetURL)) { 13561 // It's a file:// URI 13562 nsCOMPtr<nsIPrincipal> principal = NodePrincipal(); 13563 if (aReportErrors) { 13564 return NS_SUCCEEDED(principal->CheckMayLoadWithReporting( 13565 aTargetURL, false, InnerWindowID())); 13566 } 13567 return NS_SUCCEEDED(principal->CheckMayLoad(aTargetURL, false)); 13568 } 13569 13570 nsCOMPtr<nsIScriptSecurityManager> secMan = 13571 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); 13572 if (!secMan) { 13573 return false; 13574 } 13575 13576 // It's very important that we check that aTargetURL is of the same 13577 // origin as mDocumentURI, not docBaseURI, because a page can 13578 // set docBaseURI arbitrarily to any domain. 13579 bool isPrivateWin = 13580 NodePrincipal()->OriginAttributesRef().IsPrivateBrowsing(); 13581 if (NS_FAILED(secMan->CheckSameOriginURI(mDocumentURI, aTargetURL, 13582 aReportErrors, isPrivateWin))) { 13583 return false; 13584 } 13585 13586 // In addition to checking that the security manager says that 13587 // the new URI has the same origin as our current URI, we also 13588 // check that the two URIs have the same userpass. (The 13589 // security manager says that |http://foo.com| and 13590 // |http://me@foo.com| have the same origin.) mDocumentURI 13591 // won't contain the password part of the userpass, so this 13592 // means that it's never valid to specify a password in a 13593 // pushState or replaceState URI. 13594 nsAutoCString currentUserPass, newUserPass; 13595 (void)mDocumentURI->GetUserPass(currentUserPass); 13596 (void)aTargetURL->GetUserPass(newUserPass); 13597 13598 return currentUserPass.Equals(newUserPass); 13599 } 13600 13601 nsISupports* Document::GetCurrentContentSink() { 13602 return mParser ? mParser->GetContentSink() : nullptr; 13603 } 13604 13605 Document* Document::GetTemplateContentsOwner() { 13606 if (!mTemplateContentsOwner) { 13607 bool hasHadScriptObject = true; 13608 nsIScriptGlobalObject* scriptObject = 13609 GetScriptHandlingObject(hasHadScriptObject); 13610 13611 nsCOMPtr<Document> document; 13612 nsresult rv = NS_NewDOMDocument( 13613 getter_AddRefs(document), 13614 u""_ns, // aNamespaceURI 13615 u""_ns, // aQualifiedName 13616 nullptr, // aDoctype 13617 Document::GetDocumentURI(), Document::GetDocBaseURI(), NodePrincipal(), 13618 LoadedAsData::AsData, // aLoadedAsData 13619 scriptObject, // aEventObject 13620 IsHTMLDocument() ? DocumentFlavor::HTML : DocumentFlavor::XML); 13621 NS_ENSURE_SUCCESS(rv, nullptr); 13622 13623 mTemplateContentsOwner = document; 13624 NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr); 13625 13626 if (!scriptObject) { 13627 mTemplateContentsOwner->SetScopeObject(GetScopeObject()); 13628 } 13629 13630 mTemplateContentsOwner->mHasHadScriptHandlingObject = hasHadScriptObject; 13631 13632 // Set |mTemplateContentsOwner| as the template contents owner of itself so 13633 // that it is the template contents owner of nested template elements. 13634 mTemplateContentsOwner->mTemplateContentsOwner = mTemplateContentsOwner; 13635 } 13636 13637 MOZ_ASSERT(mTemplateContentsOwner->IsTemplateContentsOwner()); 13638 return mTemplateContentsOwner; 13639 } 13640 13641 // https://html.spec.whatwg.org/#the-autofocus-attribute 13642 void Document::ElementWithAutoFocusInserted(Element* aAutoFocusCandidate) { 13643 BrowsingContext* bc = GetBrowsingContext(); 13644 if (!bc) { 13645 return; 13646 } 13647 13648 // If target is not fully active, then return. 13649 if (!IsCurrentActiveDocument()) { 13650 return; 13651 } 13652 13653 // If target's active sandboxing flag set has the sandboxed automatic features 13654 // browsing context flag, then return. 13655 if (GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES) { 13656 return; 13657 } 13658 13659 // For each ancestorBC of target's browsing context's ancestor browsing 13660 // contexts: if ancestorBC's active document's origin is not same origin with 13661 // target's origin, then return. 13662 while (bc) { 13663 BrowsingContext* parent = bc->GetParent(); 13664 if (!parent) { 13665 break; 13666 } 13667 // AncestorBC is not the same site 13668 if (!parent->IsInProcess()) { 13669 return; 13670 } 13671 13672 Document* currentDocument = bc->GetDocument(); 13673 if (!currentDocument) { 13674 return; 13675 } 13676 13677 Document* parentDocument = parent->GetDocument(); 13678 if (!parentDocument) { 13679 return; 13680 } 13681 13682 // Not same origin 13683 if (!currentDocument->NodePrincipal()->Equals( 13684 parentDocument->NodePrincipal())) { 13685 return; 13686 } 13687 13688 bc = parent; 13689 } 13690 MOZ_ASSERT(bc->IsTop()); 13691 13692 Document* topDocument = bc->GetDocument(); 13693 MOZ_ASSERT(topDocument); 13694 topDocument->AppendAutoFocusCandidateToTopDocument(aAutoFocusCandidate); 13695 } 13696 13697 void Document::ScheduleFlushAutoFocusCandidates() { 13698 MOZ_ASSERT(HasAutoFocusCandidates()); 13699 MaybeScheduleRenderingPhases({RenderingPhase::FlushAutoFocusCandidates}); 13700 } 13701 13702 void Document::AppendAutoFocusCandidateToTopDocument( 13703 Element* aAutoFocusCandidate) { 13704 MOZ_ASSERT(GetBrowsingContext()->IsTop()); 13705 if (mAutoFocusFired) { 13706 return; 13707 } 13708 13709 const bool hadCandidates = HasAutoFocusCandidates(); 13710 nsWeakPtr element = do_GetWeakReference(aAutoFocusCandidate); 13711 mAutoFocusCandidates.RemoveElement(element); 13712 mAutoFocusCandidates.AppendElement(element); 13713 if (!hadCandidates) { 13714 ScheduleFlushAutoFocusCandidates(); 13715 } 13716 } 13717 13718 void Document::SetAutoFocusFired() { 13719 mAutoFocusCandidates.Clear(); 13720 mAutoFocusFired = true; 13721 } 13722 13723 // https://html.spec.whatwg.org/#flush-autofocus-candidates 13724 void Document::FlushAutoFocusCandidates() { 13725 MOZ_ASSERT(GetBrowsingContext()->IsTop()); 13726 if (mAutoFocusFired) { 13727 return; 13728 } 13729 13730 if (!mPresShell) { 13731 return; 13732 } 13733 13734 MOZ_ASSERT(HasAutoFocusCandidates()); 13735 MOZ_ASSERT(mPresShell->DidInitialize()); 13736 13737 nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetWindow(); 13738 // We should be the top document 13739 if (!topWindow) { 13740 return; 13741 } 13742 13743 #ifdef DEBUG 13744 { 13745 // Trying to find the top window (equivalent to window.top). 13746 nsCOMPtr<nsPIDOMWindowOuter> top = topWindow->GetInProcessTop(); 13747 MOZ_ASSERT(topWindow == top); 13748 } 13749 #endif 13750 13751 // Don't steal the focus from the user 13752 if (topWindow->GetFocusedElement()) { 13753 SetAutoFocusFired(); 13754 return; 13755 } 13756 13757 MOZ_ASSERT(mDocumentURI); 13758 nsAutoCString ref; 13759 // GetRef never fails 13760 nsresult rv = mDocumentURI->GetRef(ref); 13761 if (NS_SUCCEEDED(rv) && 13762 nsContentUtils::GetTargetElement(this, NS_ConvertUTF8toUTF16(ref))) { 13763 SetAutoFocusFired(); 13764 return; 13765 } 13766 13767 nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mAutoFocusCandidates); 13768 while (iter.HasMore()) { 13769 nsWeakPtr weakElement = iter.GetNext(); 13770 nsCOMPtr<Element> autoFocusElement = do_QueryReferent(weakElement); 13771 if (!autoFocusElement) { 13772 continue; 13773 } 13774 RefPtr<Document> autoFocusElementDoc = autoFocusElement->OwnerDoc(); 13775 // Get the latest info about the frame and allow scripts 13776 // to run which might affect the focusability of this element. 13777 autoFocusElementDoc->FlushPendingNotifications(FlushType::Frames); 13778 13779 // Above layout flush may cause the PresShell to disappear or autofocus to 13780 // complete. The iterator position might have changed. 13781 if (!mPresShell || mAutoFocusFired) { 13782 return; 13783 } 13784 13785 // Re-get the element because the ownerDoc() might have changed 13786 autoFocusElementDoc = autoFocusElement->OwnerDoc(); 13787 BrowsingContext* bc = autoFocusElementDoc->GetBrowsingContext(); 13788 if (!bc) { 13789 continue; 13790 } 13791 13792 // If doc is not fully active, then remove element from candidates, and 13793 // continue. 13794 if (!autoFocusElementDoc->IsCurrentActiveDocument()) { 13795 mAutoFocusCandidates.RemoveElement(weakElement); 13796 continue; 13797 } 13798 13799 nsCOMPtr<nsIContentSink> sink = 13800 do_QueryInterface(autoFocusElementDoc->GetCurrentContentSink()); 13801 if (sink) { 13802 nsHtml5TreeOpExecutor* executor = 13803 static_cast<nsHtml5TreeOpExecutor*>(sink->AsExecutor()); 13804 if (executor) { 13805 // This is a HTML5 document 13806 MOZ_ASSERT(autoFocusElementDoc->IsHTMLDocument()); 13807 // If doc's script-blocking style sheet counter is greater than 0, th 13808 // return. 13809 if (executor->WaitForPendingSheets()) { 13810 // In this case, element is the currently-best candidate, but doc is 13811 // not ready for autofocusing. We'll try again next time flush 13812 // autofocus candidates is called. 13813 ScheduleFlushAutoFocusCandidates(); 13814 return; 13815 } 13816 } 13817 } 13818 13819 // The autofocus element could be moved to a different 13820 // top level BC. 13821 if (bc->Top()->GetDocument() != this) { 13822 continue; 13823 } 13824 13825 mAutoFocusCandidates.RemoveElement(weakElement); 13826 13827 // Let inclusiveAncestorDocuments be a list consisting of doc, plus the 13828 // active documents of each of doc's browsing context's ancestor browsing 13829 // contexts. 13830 // If any Document in inclusiveAncestorDocuments has non-null target 13831 // element, then continue. 13832 bool shouldFocus = true; 13833 while (bc) { 13834 Document* doc = bc->GetDocument(); 13835 if (!doc) { 13836 shouldFocus = false; 13837 break; 13838 } 13839 13840 nsIURI* uri = doc->GetDocumentURI(); 13841 if (!uri) { 13842 shouldFocus = false; 13843 break; 13844 } 13845 13846 nsAutoCString ref; 13847 nsresult rv = uri->GetRef(ref); 13848 // If there is an element in the document tree that has an ID equal to 13849 // fragment 13850 if (NS_SUCCEEDED(rv) && 13851 nsContentUtils::GetTargetElement(doc, NS_ConvertUTF8toUTF16(ref))) { 13852 shouldFocus = false; 13853 break; 13854 } 13855 bc = bc->GetParent(); 13856 } 13857 13858 if (!shouldFocus) { 13859 continue; 13860 } 13861 13862 MOZ_ASSERT(topWindow); 13863 if (TryAutoFocusCandidate(*autoFocusElement)) { 13864 // We've successfully autofocused an element, don't 13865 // need to try to focus the rest. 13866 SetAutoFocusFired(); 13867 break; 13868 } 13869 } 13870 13871 if (HasAutoFocusCandidates()) { 13872 ScheduleFlushAutoFocusCandidates(); 13873 } 13874 } 13875 13876 bool Document::TryAutoFocusCandidate(Element& aElement) { 13877 const FocusOptions options; 13878 if (RefPtr<Element> target = nsFocusManager::GetTheFocusableArea( 13879 &aElement, nsFocusManager::ProgrammaticFocusFlags(options))) { 13880 target->Focus(options, CallerType::NonSystem, IgnoreErrors()); 13881 return true; 13882 } 13883 13884 return false; 13885 } 13886 13887 void Document::SetScrollToRef(nsIURI* aDocumentURI) { 13888 if (!aDocumentURI) { 13889 return; 13890 } 13891 13892 nsAutoCString ref; 13893 13894 // Since all URI's that pass through here aren't URL's we can't 13895 // rely on the nsIURI implementation for providing a way for 13896 // finding the 'ref' part of the URI, we'll haveto revert to 13897 // string routines for finding the data past '#' 13898 13899 nsresult rv = aDocumentURI->GetSpec(ref); 13900 if (NS_FAILED(rv)) { 13901 (void)aDocumentURI->GetRef(mScrollToRef); 13902 return; 13903 } 13904 13905 nsReadingIterator<char> start, end; 13906 13907 ref.BeginReading(start); 13908 ref.EndReading(end); 13909 13910 if (FindCharInReadable('#', start, end)) { 13911 ++start; // Skip over the '#' 13912 13913 mScrollToRef = Substring(start, end); 13914 } 13915 } 13916 13917 // https://html.spec.whatwg.org/#scrolling-to-a-fragment 13918 void Document::ScrollToRef() { 13919 RefPtr<PresShell> presShell = GetPresShell(); 13920 if (!presShell) { 13921 return; 13922 } 13923 13924 // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives 13925 // Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment: 13926 // 1. Let text directives be the document's pending text directives. 13927 const RefPtr fragmentDirective = FragmentDirective(); 13928 const nsTArray<RefPtr<nsRange>> textDirectives = 13929 fragmentDirective->FindTextFragmentsInDocument(); 13930 // 2. If ranges is non-empty, then: 13931 // 2.1 Let firstRange be the first item of ranges 13932 const RefPtr<nsRange> textDirectiveToScroll = 13933 !textDirectives.IsEmpty() ? textDirectives[0] : nullptr; 13934 // 2.2 Visually indicate each range in ranges in an implementation-defined 13935 // way. The indication must not be observable from author script. See § 3.7 13936 // Indicating The Text Match. 13937 fragmentDirective->HighlightTextDirectives(textDirectives); 13938 13939 // In a subsequent call to `ScrollToRef()` during page load, `textDirectives` 13940 // would only contain text directives that were not found in the previous 13941 // runs. If an earlier call during the same page load already found a text 13942 // directive to scroll to, only highlighting of the text directives needs to 13943 // be done. 13944 // This is indicated by `mScrolledToRefAlready`. 13945 if (mScrolledToRefAlready) { 13946 presShell->ScrollToAnchor(); 13947 return; 13948 } 13949 // 2. If fragment is the empty string and no text directives have been 13950 // scrolled to, then return the special value top of the document. 13951 if (!textDirectiveToScroll && mScrollToRef.IsEmpty()) { 13952 return; 13953 } 13954 13955 // TODO(mccr8): This will incorrectly block scrolling from a same-document 13956 // navigation triggered before the document is full loaded. See bug 1898630. 13957 if (ForceLoadAtTop()) { 13958 return; 13959 } 13960 13961 // 3. Let potentialIndicatedElement be the result of finding a potential 13962 // indicated element given document and fragment. 13963 NS_ConvertUTF8toUTF16 ref(mScrollToRef); 13964 // This also covers 2.3 of the Monkeypatch for text fragments mentioned above: 13965 // 2.3 Set firstRange as document's indicated part, return. 13966 13967 const bool scrollToTextDirective = 13968 textDirectiveToScroll 13969 ? fragmentDirective->IsTextDirectiveAllowedToBeScrolledTo() 13970 : mChangeScrollPosWhenScrollingToRef; 13971 13972 auto rv = 13973 presShell->GoToAnchor(ref, textDirectiveToScroll, scrollToTextDirective); 13974 13975 // 4. If potentialIndicatedElement is not null, then return 13976 // potentialIndicatedElement. 13977 if (NS_SUCCEEDED(rv)) { 13978 mScrolledToRefAlready = true; 13979 return; 13980 } 13981 13982 // 5. Let fragmentBytes be the result of percent-decoding fragment. 13983 nsAutoCString fragmentBytes; 13984 const bool unescaped = 13985 NS_UnescapeURL(mScrollToRef.Data(), mScrollToRef.Length(), 13986 /* aFlags = */ 0, fragmentBytes); 13987 13988 if (!unescaped || fragmentBytes.IsEmpty()) { 13989 // Another attempt is only necessary if characters were unescaped. 13990 return; 13991 } 13992 13993 // 6. Let decodedFragment be the result of running UTF-8 decode without BOM on 13994 // fragmentBytes. 13995 nsAutoString decodedFragment; 13996 rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment); 13997 NS_ENSURE_SUCCESS_VOID(rv); 13998 13999 // 7. Set potentialIndicatedElement to the result of finding a potential 14000 // indicated element given document and decodedFragment. 14001 rv = presShell->GoToAnchor(decodedFragment, nullptr, 14002 mChangeScrollPosWhenScrollingToRef); 14003 if (NS_SUCCEEDED(rv)) { 14004 mScrolledToRefAlready = true; 14005 } 14006 } 14007 14008 void Document::RegisterActivityObserver(nsISupports* aSupports) { 14009 if (!mActivityObservers) { 14010 mActivityObservers = MakeUnique<nsTHashSet<nsISupports*>>(); 14011 } 14012 mActivityObservers->Insert(aSupports); 14013 } 14014 14015 bool Document::UnregisterActivityObserver(nsISupports* aSupports) { 14016 if (!mActivityObservers) { 14017 return false; 14018 } 14019 return mActivityObservers->EnsureRemoved(aSupports); 14020 } 14021 14022 void Document::EnumerateActivityObservers( 14023 ActivityObserverEnumerator aEnumerator) { 14024 if (!mActivityObservers) { 14025 return; 14026 } 14027 14028 const auto keyArray = 14029 ToTArray<nsTArray<nsCOMPtr<nsISupports>>>(*mActivityObservers); 14030 for (auto& observer : keyArray) { 14031 aEnumerator(observer.get()); 14032 } 14033 } 14034 14035 void Document::RegisterPendingLinkUpdate(Link* aLink) { 14036 if (aLink->HasPendingLinkUpdate()) { 14037 return; 14038 } 14039 14040 aLink->SetHasPendingLinkUpdate(); 14041 14042 if (!mHasLinksToUpdateRunnable && !mFlushingPendingLinkUpdates) { 14043 nsCOMPtr<nsIRunnable> event = 14044 NewRunnableMethod("Document::FlushPendingLinkUpdates", this, 14045 &Document::FlushPendingLinkUpdates); 14046 // Do this work in a second in the worst case. 14047 nsresult rv = NS_DispatchToCurrentThreadQueue(event.forget(), 1000, 14048 EventQueuePriority::Idle); 14049 if (NS_FAILED(rv)) { 14050 // If during shutdown posting a runnable doesn't succeed, we probably 14051 // don't need to update link states. 14052 return; 14053 } 14054 mHasLinksToUpdateRunnable = true; 14055 } 14056 14057 mLinksToUpdate.InfallibleAppend(aLink); 14058 } 14059 14060 void Document::FlushPendingLinkUpdates() { 14061 MOZ_DIAGNOSTIC_ASSERT(!mFlushingPendingLinkUpdates); 14062 MOZ_ASSERT(mHasLinksToUpdateRunnable); 14063 mHasLinksToUpdateRunnable = false; 14064 14065 auto restore = MakeScopeExit([&] { mFlushingPendingLinkUpdates = false; }); 14066 mFlushingPendingLinkUpdates = true; 14067 14068 while (!mLinksToUpdate.IsEmpty()) { 14069 LinksToUpdateList links(std::move(mLinksToUpdate)); 14070 for (auto iter = links.Iter(); !iter.Done(); iter.Next()) { 14071 Link* link = iter.Get(); 14072 Element* element = link->GetElement(); 14073 if (element->OwnerDoc() == this) { 14074 link->ClearHasPendingLinkUpdate(); 14075 if (element->IsInComposedDoc()) { 14076 link->TriggerLinkUpdate(/* aNotify = */ true); 14077 } 14078 } 14079 } 14080 } 14081 } 14082 14083 /** 14084 * Retrieves the node in a static-clone document that corresponds to aOrigNode, 14085 * which is a node in the original document from which aStaticClone was cloned. 14086 */ 14087 static nsINode* GetCorrespondingNodeInDocument(const nsINode* aOrigNode, 14088 Document& aStaticClone) { 14089 MOZ_ASSERT(aOrigNode); 14090 14091 // Selections in anonymous subtrees aren't supported. 14092 if (NS_WARN_IF(aOrigNode->IsInNativeAnonymousSubtree())) { 14093 return nullptr; 14094 } 14095 14096 // If the node is disconnected, this is a bug in the selection code, but it 14097 // can happen with shadow DOM so handle it. 14098 if (NS_WARN_IF(!aOrigNode->IsInComposedDoc())) { 14099 return nullptr; 14100 } 14101 14102 AutoTArray<Maybe<uint32_t>, 32> indexArray; 14103 const nsINode* current = aOrigNode; 14104 while (const nsINode* parent = current->GetParentNode()) { 14105 Maybe<uint32_t> index = parent->ComputeIndexOf(current); 14106 NS_ENSURE_TRUE(index.isSome(), nullptr); 14107 indexArray.AppendElement(std::move(index)); 14108 current = parent; 14109 } 14110 MOZ_ASSERT(current->IsDocument() || current->IsShadowRoot()); 14111 nsINode* correspondingNode = [&]() -> nsINode* { 14112 if (current->IsDocument()) { 14113 return &aStaticClone; 14114 } 14115 const auto* shadow = ShadowRoot::FromNode(*current); 14116 if (!shadow) { 14117 return nullptr; 14118 } 14119 nsINode* correspondingHost = 14120 GetCorrespondingNodeInDocument(shadow->Host(), aStaticClone); 14121 if (NS_WARN_IF(!correspondingHost || !correspondingHost->IsElement())) { 14122 return nullptr; 14123 } 14124 return correspondingHost->AsElement()->GetShadowRoot(); 14125 }(); 14126 14127 if (NS_WARN_IF(!correspondingNode)) { 14128 return nullptr; 14129 } 14130 for (const Maybe<uint32_t>& index : Reversed(indexArray)) { 14131 correspondingNode = correspondingNode->GetChildAt_Deprecated(*index); 14132 NS_ENSURE_TRUE(correspondingNode, nullptr); 14133 } 14134 return correspondingNode; 14135 } 14136 14137 /** 14138 * Caches the selection ranges from the source document onto the static clone in 14139 * case the "Print Selection Only" functionality is invoked. 14140 * 14141 * Note that we cannot use the selection obtained from GetOriginalDocument() 14142 * since that selection may have mutated after the print was invoked. 14143 * 14144 * Note also that because nsRange objects point into a specific document's 14145 * nodes, we cannot reuse an array of nsRange objects across multiple static 14146 * clone documents. For that reason we cache a new array of ranges on each 14147 * static clone that we create. 14148 * 14149 * TODO(emilio): This can be simplified once we don't re-clone from static 14150 * documents. 14151 * 14152 * @param aSourceDoc the document from which we are caching selection ranges 14153 * @param aStaticClone the document that will hold the cache 14154 * @return true if a selection range was cached 14155 */ 14156 static void CachePrintSelectionRanges(const Document& aSourceDoc, 14157 Document& aStaticClone) { 14158 MOZ_ASSERT(aStaticClone.IsStaticDocument()); 14159 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printisfocuseddoc)); 14160 MOZ_ASSERT(!aStaticClone.GetProperty(nsGkAtoms::printselectionranges)); 14161 14162 bool sourceDocIsStatic = aSourceDoc.IsStaticDocument(); 14163 14164 // When the user opts to "Print Selection Only", the print code prefers any 14165 // selection in the static clone corresponding to the focused frame. If this 14166 // is that static clone, flag it for the printing code: 14167 const bool isFocusedDoc = [&] { 14168 if (sourceDocIsStatic) { 14169 return bool(aSourceDoc.GetProperty(nsGkAtoms::printisfocuseddoc)); 14170 } 14171 nsPIDOMWindowOuter* window = aSourceDoc.GetWindow(); 14172 if (!window) { 14173 return false; 14174 } 14175 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot(); 14176 if (!rootWindow) { 14177 return false; 14178 } 14179 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; 14180 nsFocusManager::GetFocusedDescendant(rootWindow, 14181 nsFocusManager::eIncludeAllDescendants, 14182 getter_AddRefs(focusedWindow)); 14183 return focusedWindow && focusedWindow->GetExtantDoc() == &aSourceDoc; 14184 }(); 14185 if (isFocusedDoc) { 14186 aStaticClone.SetProperty(nsGkAtoms::printisfocuseddoc, 14187 reinterpret_cast<void*>(true)); 14188 } 14189 14190 const Selection* origSelection = nullptr; 14191 const nsTArray<RefPtr<nsRange>>* origRanges = nullptr; 14192 14193 if (sourceDocIsStatic) { 14194 origRanges = static_cast<nsTArray<RefPtr<nsRange>>*>( 14195 aSourceDoc.GetProperty(nsGkAtoms::printselectionranges)); 14196 } else if (PresShell* shell = aSourceDoc.GetPresShell()) { 14197 origSelection = shell->GetCurrentSelection(SelectionType::eNormal); 14198 } 14199 14200 if (!origSelection && !origRanges) { 14201 return; 14202 } 14203 14204 const uint32_t rangeCount = 14205 sourceDocIsStatic ? origRanges->Length() : origSelection->RangeCount(); 14206 auto printRanges = MakeUnique<nsTArray<RefPtr<nsRange>>>(rangeCount); 14207 14208 for (const uint32_t i : IntegerRange(rangeCount)) { 14209 MOZ_ASSERT_IF(!sourceDocIsStatic, 14210 origSelection->RangeCount() == rangeCount); 14211 const nsRange* range = sourceDocIsStatic ? origRanges->ElementAt(i).get() 14212 : origSelection->GetRangeAt(i); 14213 MOZ_ASSERT(range); 14214 nsINode* startContainer = range->GetMayCrossShadowBoundaryStartContainer(); 14215 nsINode* endContainer = range->GetMayCrossShadowBoundaryEndContainer(); 14216 14217 if (!startContainer || !endContainer) { 14218 continue; 14219 } 14220 14221 nsINode* startNode = 14222 GetCorrespondingNodeInDocument(startContainer, aStaticClone); 14223 nsINode* endNode = 14224 GetCorrespondingNodeInDocument(endContainer, aStaticClone); 14225 14226 if (NS_WARN_IF(!startNode || !endNode)) { 14227 continue; 14228 } 14229 14230 RefPtr<nsRange> clonedRange = nsRange::Create( 14231 startNode, range->MayCrossShadowBoundaryStartOffset(), endNode, 14232 range->MayCrossShadowBoundaryEndOffset(), IgnoreErrors(), 14233 StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() 14234 ? AllowRangeCrossShadowBoundary::Yes 14235 : AllowRangeCrossShadowBoundary::No); 14236 if (clonedRange && 14237 !clonedRange->AreNormalRangeAndCrossShadowBoundaryRangeCollapsed()) { 14238 printRanges->AppendElement(std::move(clonedRange)); 14239 } 14240 } 14241 14242 if (printRanges->IsEmpty()) { 14243 return; 14244 } 14245 14246 aStaticClone.SetProperty(nsGkAtoms::printselectionranges, 14247 printRanges.release(), 14248 nsINode::DeleteProperty<nsTArray<RefPtr<nsRange>>>); 14249 } 14250 14251 already_AddRefed<Document> Document::CreateStaticClone( 14252 nsIDocShell* aCloneContainer, nsIDocumentViewer* aViewer, 14253 nsIPrintSettings* aPrintSettings, bool* aOutHasInProcessPrintCallbacks) { 14254 MOZ_ASSERT(!mCreatingStaticClone); 14255 MOZ_ASSERT(!GetProperty(nsGkAtoms::adoptedsheetclones)); 14256 MOZ_DIAGNOSTIC_ASSERT(aViewer); 14257 14258 mCreatingStaticClone = true; 14259 SetProperty(nsGkAtoms::adoptedsheetclones, new AdoptedStyleSheetCloneCache(), 14260 nsINode::DeleteProperty<AdoptedStyleSheetCloneCache>); 14261 14262 auto raii = MakeScopeExit([&] { 14263 RemoveProperty(nsGkAtoms::adoptedsheetclones); 14264 mCreatingStaticClone = false; 14265 }); 14266 14267 // Make document use different container during cloning. 14268 // 14269 // FIXME(emilio): Why is this needed? 14270 RefPtr<nsDocShell> originalShell = mDocumentContainer.get(); 14271 SetContainer(nsDocShell::Cast(aCloneContainer)); 14272 IgnoredErrorResult rv; 14273 nsCOMPtr<nsINode> clonedNode = CloneNode(true, rv); 14274 SetContainer(originalShell); 14275 if (rv.Failed()) { 14276 return nullptr; 14277 } 14278 14279 nsCOMPtr<Document> clonedDoc = do_QueryInterface(clonedNode); 14280 if (!clonedDoc) { 14281 return nullptr; 14282 } 14283 14284 // Copy any stylesheet not referenced somewhere in the DOM tree (e.g. by 14285 // `<style>`). 14286 14287 clonedDoc->CloneAdoptedSheetsFrom(*this); 14288 14289 for (int t = 0; t < AdditionalSheetTypeCount; ++t) { 14290 auto& sheets = mAdditionalSheets[additionalSheetType(t)]; 14291 for (StyleSheet* sheet : sheets) { 14292 if (sheet->IsApplicable()) { 14293 clonedDoc->AddAdditionalStyleSheet(additionalSheetType(t), sheet); 14294 } 14295 } 14296 } 14297 14298 // Font faces created with the JS API will not be reflected in the 14299 // stylesheets and need to be copied over to the cloned document. 14300 if (const FontFaceSet* set = GetFonts()) { 14301 set->CopyNonRuleFacesTo(clonedDoc->Fonts()); 14302 } 14303 14304 clonedDoc->mReferrerInfo = 14305 static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone(); 14306 clonedDoc->mPreloadReferrerInfo = clonedDoc->mReferrerInfo; 14307 CachePrintSelectionRanges(*this, *clonedDoc); 14308 14309 // We're done with the clone, embed ourselves into the document viewer and 14310 // clone our children. The order here is pretty important, because our 14311 // document our document needs to have an owner global before we can create 14312 // the frame loaders for subdocuments. 14313 aViewer->SetDocument(clonedDoc); 14314 14315 *aOutHasInProcessPrintCallbacks |= clonedDoc->HasPrintCallbacks(); 14316 14317 auto pendingClones = std::move(clonedDoc->mPendingFrameStaticClones); 14318 for (const auto& clone : pendingClones) { 14319 RefPtr<Element> element = do_QueryObject(clone.mElement); 14320 RefPtr<nsFrameLoader> frameLoader = 14321 nsFrameLoader::Create(element, /* aNetworkCreated */ false); 14322 14323 if (NS_WARN_IF(!frameLoader)) { 14324 continue; 14325 } 14326 14327 clone.mElement->SetFrameLoader(frameLoader); 14328 14329 nsresult rv = frameLoader->FinishStaticClone( 14330 clone.mStaticCloneOf, aPrintSettings, aOutHasInProcessPrintCallbacks); 14331 (void)NS_WARN_IF(NS_FAILED(rv)); 14332 } 14333 14334 return clonedDoc.forget(); 14335 } 14336 14337 void Document::UnlinkOriginalDocumentIfStatic() { 14338 if (IsStaticDocument() && mOriginalDocument) { 14339 MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0); 14340 mOriginalDocument->mStaticCloneCount--; 14341 mOriginalDocument = nullptr; 14342 } 14343 MOZ_ASSERT(!mOriginalDocument); 14344 } 14345 14346 nsresult Document::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback, 14347 uint32_t* aHandle) { 14348 const bool wasEmpty = mFrameRequestManager.IsEmpty(); 14349 MOZ_TRY(mFrameRequestManager.Schedule(aCallback, aHandle)); 14350 if (wasEmpty) { 14351 MaybeScheduleFrameRequestCallbacks(); 14352 } 14353 return NS_OK; 14354 } 14355 14356 void Document::CancelFrameRequestCallback(uint32_t aHandle) { 14357 mFrameRequestManager.Cancel(aHandle); 14358 } 14359 14360 void Document::ScheduleVideoFrameCallbacks(HTMLVideoElement* aElement) { 14361 const bool wasEmpty = mFrameRequestManager.IsEmpty(); 14362 mFrameRequestManager.Schedule(aElement); 14363 if (wasEmpty) { 14364 MaybeScheduleFrameRequestCallbacks(); 14365 } 14366 } 14367 14368 void Document::CancelVideoFrameCallbacks(HTMLVideoElement* aElement) { 14369 mFrameRequestManager.Cancel(aElement); 14370 } 14371 14372 nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) { 14373 // Get the document's current state object. This is the object backing both 14374 // history.state and popStateEvent.state. 14375 // 14376 // mStateObjectContainer may be null; this just means that there's no 14377 // current state object. 14378 14379 if (!mCachedStateObjectValid) { 14380 if (mStateObjectContainer) { 14381 AutoJSAPI jsapi; 14382 // Init with null is "OK" in the sense that it will just fail. 14383 if (!jsapi.Init(GetScopeObject())) { 14384 return NS_ERROR_UNEXPECTED; 14385 } 14386 JS::Rooted<JS::Value> value(jsapi.cx()); 14387 nsresult rv = 14388 mStateObjectContainer->DeserializeToJsval(jsapi.cx(), &value); 14389 NS_ENSURE_SUCCESS(rv, rv); 14390 14391 mCachedStateObject = value; 14392 if (!value.isNullOrUndefined()) { 14393 mozilla::HoldJSObjects(this); 14394 } 14395 } else { 14396 mCachedStateObject = JS::NullValue(); 14397 } 14398 mCachedStateObjectValid = true; 14399 } 14400 14401 aState.set(mCachedStateObject); 14402 return NS_OK; 14403 } 14404 14405 void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) { 14406 mTiming = aTiming; 14407 if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) { 14408 mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), 14409 mLoadingOrRestoredFromBFCacheTimeStamp); 14410 } 14411 14412 // If there's already the DocumentTimeline instance, tell it since the 14413 // DocumentTimeline is based on both the navigation start time stamp and the 14414 // refresh driver timestamp. 14415 if (mDocumentTimeline) { 14416 mDocumentTimeline->UpdateLastRefreshDriverTime(); 14417 } 14418 } 14419 14420 nsContentList* Document::ImageMapList() { 14421 if (!mImageMaps) { 14422 mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map, 14423 nsGkAtoms::map); 14424 } 14425 14426 return mImageMaps; 14427 } 14428 14429 #define DEPRECATED_OPERATION(_op) #_op "Warning", 14430 static const char* kDeprecationWarnings[] = { 14431 #include "nsDeprecatedOperationList.h" 14432 nullptr}; 14433 #undef DEPRECATED_OPERATION 14434 14435 #define DOCUMENT_WARNING(_op) #_op "Warning", 14436 static const char* kDocumentWarnings[] = { 14437 #include "nsDocumentWarningList.h" 14438 nullptr}; 14439 #undef DOCUMENT_WARNING 14440 14441 static UseCounter OperationToUseCounter(DeprecatedOperations aOperation) { 14442 switch (aOperation) { 14443 #define DEPRECATED_OPERATION(_op) \ 14444 case DeprecatedOperations::e##_op: \ 14445 return eUseCounter_##_op; 14446 #include "nsDeprecatedOperationList.h" 14447 #undef DEPRECATED_OPERATION 14448 default: 14449 MOZ_CRASH(); 14450 } 14451 } 14452 14453 bool Document::HasWarnedAbout(DeprecatedOperations aOperation) const { 14454 return mDeprecationWarnedAbout[static_cast<size_t>(aOperation)]; 14455 } 14456 14457 void Document::WarnOnceAbout( 14458 DeprecatedOperations aOperation, bool asError /* = false */, 14459 const nsTArray<nsString>& aParams /* = empty array */) const { 14460 MOZ_ASSERT(NS_IsMainThread()); 14461 if (HasWarnedAbout(aOperation)) { 14462 return; 14463 } 14464 mDeprecationWarnedAbout[static_cast<size_t>(aOperation)] = true; 14465 const_cast<Document*>(this)->SetUseCounter(OperationToUseCounter(aOperation)); 14466 uint32_t flags = 14467 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag; 14468 nsContentUtils::ReportToConsole( 14469 flags, "DOM Core"_ns, this, nsContentUtils::eDOM_PROPERTIES, 14470 kDeprecationWarnings[static_cast<size_t>(aOperation)], aParams); 14471 } 14472 14473 bool Document::HasWarnedAbout(DocumentWarnings aWarning) const { 14474 return mDocWarningWarnedAbout[aWarning]; 14475 } 14476 14477 void Document::WarnOnceAbout( 14478 DocumentWarnings aWarning, bool asError /* = false */, 14479 const nsTArray<nsString>& aParams /* = empty array */) const { 14480 MOZ_ASSERT(NS_IsMainThread()); 14481 if (HasWarnedAbout(aWarning)) { 14482 return; 14483 } 14484 mDocWarningWarnedAbout[aWarning] = true; 14485 uint32_t flags = 14486 asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag; 14487 nsContentUtils::ReportToConsole(flags, "DOM Core"_ns, this, 14488 nsContentUtils::eDOM_PROPERTIES, 14489 kDocumentWarnings[aWarning], aParams); 14490 } 14491 14492 void Document::TrackImage(imgIRequest* aImage) { 14493 MOZ_ASSERT(aImage); 14494 bool newAnimation = false; 14495 mTrackedImages.WithEntryHandle(aImage, [&](auto&& entry) { 14496 if (entry) { 14497 // The image is already in the hashtable. Increment its count. 14498 uint32_t oldCount = entry.Data(); 14499 MOZ_ASSERT(oldCount > 0, "Entry in the image tracker with count 0!"); 14500 entry.Data() = oldCount + 1; 14501 } else { 14502 // A new entry was inserted - set the count to 1. 14503 entry.Insert(1); 14504 14505 // If we're locking images, lock this image too. 14506 if (mLockingImages) { 14507 aImage->LockImage(); 14508 } 14509 14510 // If we're animating images, request that this image be animated too. 14511 if (mAnimatingImages) { 14512 aImage->IncrementAnimationConsumers(); 14513 newAnimation = true; 14514 } 14515 } 14516 }); 14517 if (newAnimation) { 14518 AnimatedImageStateMaybeChanged(true); 14519 } 14520 } 14521 14522 void Document::UntrackImage(imgIRequest* aImage, 14523 RequestDiscard aRequestDiscard) { 14524 MOZ_ASSERT(aImage); 14525 14526 // Get the old count. It should exist and be > 0. 14527 auto entry = mTrackedImages.Lookup(aImage); 14528 if (!entry) { 14529 MOZ_ASSERT_UNREACHABLE("Removing image that wasn't in the tracker!"); 14530 return; 14531 } 14532 MOZ_ASSERT(entry.Data() > 0, "Entry in the image tracker with count 0!"); 14533 // If the count becomes zero, remove it from the tracker. 14534 if (--entry.Data() == 0) { 14535 entry.Remove(); 14536 } else { 14537 return; 14538 } 14539 14540 // Now that we're no longer tracking this image, unlock it if we'd 14541 // previously locked it. 14542 if (mLockingImages) { 14543 aImage->UnlockImage(); 14544 } 14545 14546 // If we're animating images, remove our request to animate this one. 14547 if (mAnimatingImages) { 14548 aImage->DecrementAnimationConsumers(); 14549 AnimatedImageStateMaybeChanged(false); 14550 } 14551 14552 if (aRequestDiscard == RequestDiscard::Yes) { 14553 // Do this even if !mLocking, because even if we didn't just unlock 14554 // this image, it might still be a candidate for discarding. 14555 aImage->RequestDiscard(); 14556 } 14557 } 14558 14559 void Document::PropagateMediaFeatureChangeToTrackedImages( 14560 const MediaFeatureChange& aChange) { 14561 // Inform every content image used in the document that media feature values 14562 // have changed. Pull the images out into a set and iterate over them, in case 14563 // the image notifications do something that ends up modifying the table. 14564 nsTHashSet<nsRefPtrHashKey<imgIContainer>> images; 14565 for (imgIRequest* req : mTrackedImages.Keys()) { 14566 nsCOMPtr<imgIContainer> image; 14567 req->GetImage(getter_AddRefs(image)); 14568 if (!image) { 14569 continue; 14570 } 14571 image = image->Unwrap(); 14572 images.Insert(image); 14573 } 14574 for (imgIContainer* image : images) { 14575 image->MediaFeatureValuesChangedAllDocuments(aChange); 14576 } 14577 } 14578 14579 void Document::SetLockingImages(bool aLocking) { 14580 // If there's no change, there's nothing to do. 14581 if (mLockingImages == aLocking) { 14582 return; 14583 } 14584 14585 // Otherwise, iterate over our images and perform the appropriate action. 14586 for (imgIRequest* image : mTrackedImages.Keys()) { 14587 if (aLocking) { 14588 image->LockImage(); 14589 } else { 14590 image->UnlockImage(); 14591 } 14592 } 14593 14594 // Update state. 14595 mLockingImages = aLocking; 14596 } 14597 14598 void Document::SetImageAnimationState(bool aAnimating) { 14599 // If there's no change, there's nothing to do. 14600 if (mAnimatingImages == aAnimating) { 14601 return; 14602 } 14603 14604 // Otherwise, iterate over our images and perform the appropriate action. 14605 for (imgIRequest* image : mTrackedImages.Keys()) { 14606 if (aAnimating) { 14607 image->IncrementAnimationConsumers(); 14608 } else { 14609 image->DecrementAnimationConsumers(); 14610 } 14611 } 14612 14613 AnimatedImageStateMaybeChanged(aAnimating); 14614 14615 // Update state. 14616 mAnimatingImages = aAnimating; 14617 } 14618 14619 void Document::AnimatedImageStateMaybeChanged(bool aAnimating) { 14620 auto* ps = GetPresShell(); 14621 if (!ps) { 14622 return; 14623 } 14624 auto* pc = ps->GetPresContext(); 14625 if (!pc) { 14626 return; 14627 } 14628 auto* rd = pc->RefreshDriver(); 14629 if (aAnimating) { 14630 rd->StartTimerForAnimatedImagesIfNeeded(); 14631 } else { 14632 rd->StopTimerForAnimatedImagesIfNeeded(); 14633 } 14634 } 14635 14636 void Document::ScheduleSVGUseElementShadowTreeUpdate( 14637 SVGUseElement& aUseElement) { 14638 MOZ_ASSERT(aUseElement.IsInComposedDoc()); 14639 14640 if (MOZ_UNLIKELY(mIsStaticDocument)) { 14641 // Printing doesn't deal well with dynamic DOM mutations. 14642 return; 14643 } 14644 14645 mSVGUseElementsNeedingShadowTreeUpdate.Insert(&aUseElement); 14646 14647 if (PresShell* presShell = GetPresShell()) { 14648 presShell->EnsureStyleFlush(); 14649 } 14650 } 14651 14652 void Document::DoUpdateSVGUseElementShadowTrees() { 14653 MOZ_ASSERT(!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty()); 14654 14655 MOZ_ASSERT(!mCloningForSVGUse); 14656 nsAutoScriptBlockerSuppressNodeRemoved blocker; 14657 mCloningForSVGUse = true; 14658 14659 do { 14660 const auto useElementsToUpdate = ToTArray<nsTArray<RefPtr<SVGUseElement>>>( 14661 mSVGUseElementsNeedingShadowTreeUpdate); 14662 mSVGUseElementsNeedingShadowTreeUpdate.Clear(); 14663 14664 for (const auto& useElement : useElementsToUpdate) { 14665 if (MOZ_UNLIKELY(!useElement->IsInComposedDoc())) { 14666 // The element was in another <use> shadow tree which we processed 14667 // already and also needed an update, and is removed from the document 14668 // now, so nothing to do here. 14669 MOZ_ASSERT(useElementsToUpdate.Length() > 1); 14670 continue; 14671 } 14672 useElement->UpdateShadowTree(); 14673 } 14674 } while (!mSVGUseElementsNeedingShadowTreeUpdate.IsEmpty()); 14675 14676 mCloningForSVGUse = false; 14677 } 14678 14679 void Document::NotifyMediaFeatureValuesChanged() { 14680 for (RefPtr<HTMLImageElement> imageElement : mResponsiveContent) { 14681 imageElement->MediaFeatureValuesChanged(); 14682 } 14683 } 14684 14685 already_AddRefed<Touch> Document::CreateTouch( 14686 nsGlobalWindowInner* aView, EventTarget* aTarget, int32_t aIdentifier, 14687 int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY, 14688 int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY, 14689 float aRotationAngle, float aForce) { 14690 RefPtr<Touch> touch = 14691 new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY, 14692 aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce); 14693 return touch.forget(); 14694 } 14695 14696 already_AddRefed<TouchList> Document::CreateTouchList() { 14697 RefPtr<TouchList> retval = new TouchList(ToSupports(this)); 14698 return retval.forget(); 14699 } 14700 14701 already_AddRefed<TouchList> Document::CreateTouchList( 14702 Touch& aTouch, const Sequence<OwningNonNull<Touch>>& aTouches) { 14703 RefPtr<TouchList> retval = new TouchList(ToSupports(this)); 14704 retval->Append(&aTouch); 14705 for (uint32_t i = 0; i < aTouches.Length(); ++i) { 14706 retval->Append(aTouches[i].get()); 14707 } 14708 return retval.forget(); 14709 } 14710 14711 already_AddRefed<TouchList> Document::CreateTouchList( 14712 const Sequence<OwningNonNull<Touch>>& aTouches) { 14713 RefPtr<TouchList> retval = new TouchList(ToSupports(this)); 14714 for (uint32_t i = 0; i < aTouches.Length(); ++i) { 14715 retval->Append(aTouches[i].get()); 14716 } 14717 return retval.forget(); 14718 } 14719 14720 // https://drafts.csswg.org/cssom-view/Overview#dom-document-caretpositionfrompoint 14721 already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint( 14722 float aX, float aY, const CaretPositionFromPointOptions& aOptions) { 14723 using FrameForPointOption = nsLayoutUtils::FrameForPointOption; 14724 14725 nscoord x = nsPresContext::CSSPixelsToAppUnits(aX); 14726 nscoord y = nsPresContext::CSSPixelsToAppUnits(aY); 14727 nsPoint pt(x, y); 14728 14729 FlushPendingNotifications(FlushType::Layout); 14730 14731 PresShell* presShell = GetPresShell(); 14732 if (!presShell) { 14733 return nullptr; 14734 } 14735 14736 nsIFrame* rootFrame = presShell->GetRootFrame(); 14737 14738 // XUL docs, unlike HTML, have no frame tree until everything's done loading 14739 if (!rootFrame) { 14740 return nullptr; 14741 } 14742 14743 nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint( 14744 RelativeTo{rootFrame}, pt, 14745 {{FrameForPointOption::IgnorePaintSuppression, 14746 FrameForPointOption::IgnoreCrossDoc}}); 14747 if (!ptFrame) { 14748 return nullptr; 14749 } 14750 14751 // We require frame-relative coordinates for GetContentOffsetsFromPoint. 14752 nsPoint adjustedPoint = pt; 14753 if (nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame}, 14754 adjustedPoint) != 14755 nsLayoutUtils::TRANSFORM_SUCCEEDED) { 14756 return nullptr; 14757 } 14758 14759 nsIFrame::ContentOffsets offsets = 14760 ptFrame->GetContentOffsetsFromPoint(adjustedPoint); 14761 14762 nsCOMPtr<nsINode> node = offsets.content; 14763 uint32_t offset = offsets.offset; 14764 nsCOMPtr<nsINode> anonNode = node; 14765 bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree(); 14766 if (nodeIsAnonymous) { 14767 node = ptFrame->GetContent(); 14768 nsINode* nonChrome = 14769 node->AsContent()->FindFirstNonChromeOnlyAccessContent(); 14770 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonChrome); 14771 nsTextControlFrame* textFrame = 14772 do_QueryFrame(nonChrome->AsContent()->GetPrimaryFrame()); 14773 if (!textFrame) { 14774 return nullptr; 14775 } 14776 14777 // If the anonymous content node has a child, then we need to make sure 14778 // that we get the appropriate child, as otherwise the offset may not be 14779 // correct when we construct a range for it. 14780 nsCOMPtr<nsINode> firstChild = anonNode->GetFirstChild(); 14781 if (firstChild) { 14782 anonNode = firstChild; 14783 } 14784 14785 if (textArea) { 14786 offset = nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset); 14787 } 14788 14789 node = nonChrome; 14790 } 14791 14792 bool offsetAndNodeNeedsAdjustment = false; 14793 14794 if (StaticPrefs:: 14795 dom_shadowdom_new_caretPositionFromPoint_behavior_enabled()) { 14796 while (node->IsInShadowTree() && 14797 !aOptions.mShadowRoots.Contains(node->GetContainingShadow())) { 14798 node = node->GetContainingShadowHost(); 14799 offsetAndNodeNeedsAdjustment = true; 14800 } 14801 } 14802 14803 if (offsetAndNodeNeedsAdjustment) { 14804 const Maybe<uint32_t> maybeIndex = node->ComputeIndexInParentContent(); 14805 if (MOZ_UNLIKELY(maybeIndex.isNothing())) { 14806 // Unlikely to happen, but still return nullptr to avoid leaking 14807 // information about the shadow tree. 14808 return nullptr; 14809 } 14810 // 5.3.1: Set startOffset to index of startNode’s root's host. 14811 offset = maybeIndex.value(); 14812 // 5.3.2: Set startNode to startNode’s root's host's parent. 14813 node = node->GetParentNode(); 14814 } 14815 14816 RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset); 14817 if (nodeIsAnonymous) { 14818 aCaretPos->SetAnonymousContentNode(anonNode); 14819 } 14820 return aCaretPos.forget(); 14821 } 14822 14823 bool Document::IsPotentiallyScrollable(HTMLBodyElement* aBody) { 14824 // We rely on correct frame information here, so need to flush frames. 14825 FlushPendingNotifications(FlushType::Frames); 14826 14827 // An element that is the HTML body element is potentially scrollable if all 14828 // of the following conditions are true: 14829 14830 // The element has an associated CSS layout box. 14831 nsIFrame* bodyFrame = nsLayoutUtils::GetStyleFrame(aBody); 14832 if (!bodyFrame) { 14833 return false; 14834 } 14835 14836 // The element's parent element's computed value of the overflow-x and 14837 // overflow-y properties are visible. 14838 MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement()); 14839 nsIFrame* parentFrame = nsLayoutUtils::GetStyleFrame(aBody->GetParent()); 14840 if (parentFrame && 14841 parentFrame->StyleDisplay()->OverflowIsVisibleInBothAxis()) { 14842 return false; 14843 } 14844 14845 // The element's computed value of the overflow-x or overflow-y properties is 14846 // not visible. 14847 return !bodyFrame->StyleDisplay()->OverflowIsVisibleInBothAxis(); 14848 } 14849 14850 Element* Document::GetScrollingElement() { 14851 // Keep this in sync with IsScrollingElement. 14852 if (GetCompatibilityMode() == eCompatibility_NavQuirks) { 14853 RefPtr<HTMLBodyElement> body = GetBodyElement(); 14854 if (body && !IsPotentiallyScrollable(body)) { 14855 return body; 14856 } 14857 14858 return nullptr; 14859 } 14860 14861 return GetRootElement(); 14862 } 14863 14864 bool Document::IsScrollingElement(Element* aElement) { 14865 // Keep this in sync with GetScrollingElement. 14866 MOZ_ASSERT(aElement); 14867 14868 if (GetCompatibilityMode() != eCompatibility_NavQuirks) { 14869 return aElement == GetRootElement(); 14870 } 14871 14872 // In the common case when aElement != body, avoid refcounting. 14873 HTMLBodyElement* body = GetBodyElement(); 14874 if (aElement != body) { 14875 return false; 14876 } 14877 14878 // Now we know body is non-null, since aElement is not null. It's the 14879 // scrolling element for the document if it itself is not potentially 14880 // scrollable. 14881 RefPtr<HTMLBodyElement> strongBody(body); 14882 return !IsPotentiallyScrollable(strongBody); 14883 } 14884 14885 class UnblockParsingPromiseHandler final : public PromiseNativeHandler { 14886 public: 14887 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 14888 NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler) 14889 14890 explicit UnblockParsingPromiseHandler(Document* aDocument, Promise* aPromise, 14891 const BlockParsingOptions& aOptions) 14892 : mPromise(aPromise) { 14893 nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull(); 14894 // Parser blocking is not allowed for about:blank 14895 if (parser && !parser->IsAboutBlankMode() && 14896 (aOptions.mBlockScriptCreated || !parser->IsScriptCreated())) { 14897 parser->BlockParser(); 14898 mParser = do_GetWeakReference(parser); 14899 mDocument = aDocument; 14900 mDocument->BlockOnload(); 14901 mDocument->BlockDOMContentLoaded(); 14902 } 14903 } 14904 14905 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 14906 ErrorResult& aRv) override { 14907 MaybeUnblockParser(); 14908 14909 mPromise->MaybeResolve(aValue); 14910 } 14911 14912 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 14913 ErrorResult& aRv) override { 14914 MaybeUnblockParser(); 14915 14916 mPromise->MaybeReject(aValue); 14917 } 14918 14919 protected: 14920 virtual ~UnblockParsingPromiseHandler() { 14921 // If we're being cleaned up by the cycle collector, our mDocument reference 14922 // may have been unlinked while our mParser weak reference is still alive. 14923 if (mDocument) { 14924 MaybeUnblockParser(); 14925 } 14926 } 14927 14928 private: 14929 void MaybeUnblockParser() { 14930 nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser); 14931 if (parser) { 14932 MOZ_DIAGNOSTIC_ASSERT(mDocument); 14933 nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull(); 14934 if (parser == docParser) { 14935 parser->UnblockParser(); 14936 parser->ContinueInterruptedParsingAsync(); 14937 } 14938 } 14939 if (mDocument) { 14940 // We blocked DOMContentLoaded and load events on this document. Unblock 14941 // them. Note that we want to do that no matter what's going on with the 14942 // parser state for this document. Maybe someone caused it to stop being 14943 // parsed, so CreatorParserOrNull() is returning null, but we still want 14944 // to unblock these. 14945 mDocument->UnblockDOMContentLoaded(); 14946 mDocument->UnblockOnload(false); 14947 } 14948 mParser = nullptr; 14949 mDocument = nullptr; 14950 } 14951 14952 nsWeakPtr mParser; 14953 RefPtr<Promise> mPromise; 14954 RefPtr<Document> mDocument; 14955 }; 14956 14957 NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise) 14958 14959 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler) 14960 NS_INTERFACE_MAP_ENTRY(nsISupports) 14961 NS_INTERFACE_MAP_END 14962 14963 NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler) 14964 NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler) 14965 14966 void Document::SetForceNonNativeTheme(bool aForce) { 14967 if (mForceNonNativeTheme == aForce) { 14968 return; 14969 } 14970 mForceNonNativeTheme = aForce; 14971 if (auto* pc = GetPresContext()) { 14972 pc->MediaFeatureValuesChanged( 14973 {MediaFeatureChangeReason::PreferenceChange}, 14974 MediaFeatureChangePropagation::JustThisDocument); 14975 } 14976 } 14977 14978 already_AddRefed<Promise> Document::BlockParsing( 14979 Promise& aPromise, const BlockParsingOptions& aOptions, ErrorResult& aRv) { 14980 RefPtr<Promise> resultPromise = 14981 Promise::Create(aPromise.GetParentObject(), aRv); 14982 if (aRv.Failed()) { 14983 return nullptr; 14984 } 14985 14986 RefPtr<PromiseNativeHandler> promiseHandler = 14987 new UnblockParsingPromiseHandler(this, resultPromise, aOptions); 14988 aPromise.AppendNativeHandler(promiseHandler); 14989 14990 return resultPromise.forget(); 14991 } 14992 14993 already_AddRefed<nsIURI> Document::GetMozDocumentURIIfNotForErrorPages() { 14994 if (mFailedChannel) { 14995 nsCOMPtr<nsIURI> failedURI; 14996 if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) { 14997 return failedURI.forget(); 14998 } 14999 } 15000 15001 nsCOMPtr<nsIURI> uri = GetDocumentURIObject(); 15002 if (!uri) { 15003 return nullptr; 15004 } 15005 15006 return uri.forget(); 15007 } 15008 15009 Promise* Document::GetDocumentReadyForIdle(ErrorResult& aRv) { 15010 if (mIsGoingAway) { 15011 aRv.Throw(NS_ERROR_NOT_AVAILABLE); 15012 return nullptr; 15013 } 15014 15015 if (!mReadyForIdle) { 15016 nsIGlobalObject* global = GetScopeObject(); 15017 if (!global) { 15018 aRv.Throw(NS_ERROR_NOT_AVAILABLE); 15019 return nullptr; 15020 } 15021 15022 mReadyForIdle = Promise::Create(global, aRv); 15023 if (aRv.Failed()) { 15024 return nullptr; 15025 } 15026 } 15027 15028 return mReadyForIdle; 15029 } 15030 15031 void Document::MaybeResolveReadyForIdle() { 15032 IgnoredErrorResult rv; 15033 Promise* readyPromise = GetDocumentReadyForIdle(rv); 15034 if (readyPromise) { 15035 readyPromise->MaybeResolveWithUndefined(); 15036 } 15037 } 15038 15039 mozilla::dom::FeaturePolicy* Document::FeaturePolicy() const { 15040 // The policy is created when the document is initialized. We _must_ have a 15041 // policy here even if the featurePolicy pref is off. If this assertion fails, 15042 // it means that ::FeaturePolicy() is called before ::StartDocumentLoad(). 15043 MOZ_ASSERT(mFeaturePolicy); 15044 return mFeaturePolicy; 15045 } 15046 15047 nsIDOMXULCommandDispatcher* Document::GetCommandDispatcher() { 15048 // Only chrome documents are allowed to use command dispatcher. 15049 if (!nsContentUtils::IsChromeDoc(this)) { 15050 return nullptr; 15051 } 15052 if (!mCommandDispatcher) { 15053 // Create our command dispatcher and hook it up. 15054 mCommandDispatcher = new nsXULCommandDispatcher(this); 15055 } 15056 return mCommandDispatcher; 15057 } 15058 15059 void Document::InitializeXULBroadcastManager() { 15060 if (mXULBroadcastManager) { 15061 return; 15062 } 15063 mXULBroadcastManager = new XULBroadcastManager(this); 15064 } 15065 15066 namespace { 15067 15068 class DevToolsMutationObserver final : public nsStubMutationObserver { 15069 NS_DECL_ISUPPORTS 15070 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED 15071 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED 15072 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED 15073 15074 // We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools 15075 // relies on the event firing _before_ the removal happens. 15076 // NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED 15077 15078 // NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character 15079 // data changes right now (maybe intentionally?). 15080 // NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED 15081 15082 DevToolsMutationObserver() = default; 15083 15084 private: 15085 void FireEvent(nsINode* aTarget, const nsAString& aType); 15086 15087 ~DevToolsMutationObserver() = default; 15088 }; 15089 15090 NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver) 15091 15092 void DevToolsMutationObserver::FireEvent(nsINode* aTarget, 15093 const nsAString& aType) { 15094 AsyncEventDispatcher::RunDOMEventWhenSafe(*aTarget, aType, CanBubble::eNo, 15095 ChromeOnlyDispatch::eYes, 15096 Composed::eYes); 15097 } 15098 15099 void DevToolsMutationObserver::AttributeChanged(Element* aElement, 15100 int32_t aNamespaceID, 15101 nsAtom* aAttribute, AttrModType, 15102 const nsAttrValue* aOldValue) { 15103 FireEvent(aElement, u"devtoolsattrmodified"_ns); 15104 } 15105 15106 void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent, 15107 const ContentAppendInfo& aInfo) { 15108 for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) { 15109 ContentInserted(c, aInfo); 15110 } 15111 } 15112 15113 void DevToolsMutationObserver::ContentInserted(nsIContent* aChild, 15114 const ContentInsertInfo&) { 15115 FireEvent(aChild, u"devtoolschildinserted"_ns); 15116 } 15117 15118 static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver; 15119 15120 } // namespace 15121 15122 void Document::SetDevToolsWatchingDOMMutations(bool aValue) { 15123 if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) { 15124 return; 15125 } 15126 mDevToolsWatchingDOMMutations = aValue; 15127 if (aValue) { 15128 if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) { 15129 sDevToolsMutationObserver = new DevToolsMutationObserver(); 15130 ClearOnShutdown(&sDevToolsMutationObserver); 15131 } 15132 AddMutationObserver(sDevToolsMutationObserver); 15133 } else if (sDevToolsMutationObserver) { 15134 RemoveMutationObserver(sDevToolsMutationObserver); 15135 } 15136 } 15137 15138 void EvaluateMediaQueryLists(nsTArray<RefPtr<MediaQueryList>>& aListsToNotify, 15139 Document& aDocument) { 15140 if (nsPresContext* pc = aDocument.GetPresContext()) { 15141 pc->FlushPendingMediaFeatureValuesChanged(); 15142 } 15143 15144 for (MediaQueryList* mql : aDocument.MediaQueryLists()) { 15145 if (mql->EvaluateOnRenderingUpdate()) { 15146 aListsToNotify.AppendElement(mql); 15147 } 15148 } 15149 } 15150 15151 void Document::EvaluateMediaQueriesAndReportChanges() { 15152 AutoTArray<RefPtr<MediaQueryList>, 32> mqls; 15153 EvaluateMediaQueryLists(mqls, *this); 15154 for (auto& mql : mqls) { 15155 mql->FireChangeEvent(); 15156 } 15157 } 15158 15159 nsIHTMLCollection* Document::Children() { 15160 if (!mChildrenCollection) { 15161 mChildrenCollection = 15162 new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk, 15163 nsGkAtoms::_asterisk, false); 15164 } 15165 15166 return mChildrenCollection; 15167 } 15168 15169 uint32_t Document::ChildElementCount() { return Children()->Length(); } 15170 15171 // Singleton class to manage the list of fullscreen documents which are the 15172 // root of a branch which contains fullscreen documents. We maintain this list 15173 // so that we can easily exit all windows from fullscreen when the user 15174 // presses the escape key. 15175 class FullscreenRoots { 15176 public: 15177 // Adds the root of given document to the manager. Calling this method 15178 // with a document whose root is already contained has no effect. 15179 static void Add(Document* aDoc); 15180 15181 // Iterates over every root in the root list, and calls aFunction, passing 15182 // each root once to aFunction. It is safe to call Add() and Remove() while 15183 // iterating over the list (i.e. in aFunction). Documents that are removed 15184 // from the manager during traversal are not traversed, and documents that 15185 // are added to the manager during traversal are also not traversed. 15186 static void ForEach(void (*aFunction)(Document* aDoc)); 15187 15188 // Removes the root of a specific document from the manager. 15189 static void Remove(Document* aDoc); 15190 15191 // Returns true if all roots added to the list have been removed. 15192 static bool IsEmpty(); 15193 15194 private: 15195 MOZ_COUNTED_DEFAULT_CTOR(FullscreenRoots) 15196 MOZ_COUNTED_DTOR(FullscreenRoots) 15197 15198 using RootsArray = nsTArray<WeakPtr<Document>>; 15199 15200 // Returns true if aRoot is in the list of fullscreen roots. 15201 static bool Contains(Document* aRoot); 15202 15203 // Singleton instance of the FullscreenRoots. This is instantiated when a 15204 // root is added, and it is deleted when the last root is removed. 15205 static FullscreenRoots* sInstance; 15206 15207 // List of weak pointers to roots. 15208 RootsArray mRoots; 15209 }; 15210 15211 FullscreenRoots* FullscreenRoots::sInstance = nullptr; 15212 15213 /* static */ 15214 void FullscreenRoots::ForEach(void (*aFunction)(Document* aDoc)) { 15215 if (!sInstance) { 15216 return; 15217 } 15218 // Create a copy of the roots array, and iterate over the copy. This is so 15219 // that if an element is removed from mRoots we don't mess up our iteration. 15220 RootsArray roots(sInstance->mRoots.Clone()); 15221 // Call aFunction on all entries. 15222 for (uint32_t i = 0; i < roots.Length(); i++) { 15223 nsCOMPtr<Document> root(roots[i]); 15224 // Check that the root isn't in the manager. This is so that new additions 15225 // while we were running don't get traversed. 15226 if (root && FullscreenRoots::Contains(root)) { 15227 aFunction(root); 15228 } 15229 } 15230 } 15231 15232 /* static */ 15233 bool FullscreenRoots::Contains(Document* aRoot) { 15234 return sInstance && sInstance->mRoots.Contains(aRoot); 15235 } 15236 15237 /* static */ 15238 void FullscreenRoots::Add(Document* aDoc) { 15239 nsCOMPtr<Document> root = 15240 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc); 15241 if (!FullscreenRoots::Contains(root)) { 15242 if (!sInstance) { 15243 sInstance = new FullscreenRoots(); 15244 } 15245 sInstance->mRoots.AppendElement(root); 15246 } 15247 } 15248 15249 /* static */ 15250 void FullscreenRoots::Remove(Document* aDoc) { 15251 nsCOMPtr<Document> root = 15252 nsContentUtils::GetInProcessSubtreeRootDocument(aDoc); 15253 if (!sInstance || !sInstance->mRoots.RemoveElement(root)) { 15254 NS_ERROR("Should only try to remove roots which are still added!"); 15255 return; 15256 } 15257 if (sInstance->mRoots.IsEmpty()) { 15258 delete sInstance; 15259 sInstance = nullptr; 15260 } 15261 } 15262 15263 /* static */ 15264 bool FullscreenRoots::IsEmpty() { return !sInstance; } 15265 15266 // Any fullscreen change waiting for the widget to finish transition 15267 // is queued here. This is declared static instead of a member of 15268 // Document because in the majority of time, there would be at most 15269 // one document requesting or exiting fullscreen. We shouldn't waste 15270 // the space to hold for it in every document. 15271 class PendingFullscreenChangeList { 15272 public: 15273 PendingFullscreenChangeList() = delete; 15274 15275 template <typename T> 15276 static void Add(UniquePtr<T> aChange) { 15277 sList.insertBack(aChange.release()); 15278 } 15279 15280 static const FullscreenChange* GetLast() { return sList.getLast(); } 15281 15282 enum IteratorOption { 15283 // When we are committing fullscreen changes or preparing for 15284 // that, we generally want to iterate all requests in the same 15285 // window with eDocumentsWithSameRoot option. 15286 eDocumentsWithSameRoot, 15287 // If we are removing a document from the tree, we would only 15288 // want to remove the requests from the given document and its 15289 // descendants. For that case, use eInclusiveDescendants. 15290 eInclusiveDescendants 15291 }; 15292 15293 template <typename T> 15294 class Iterator { 15295 public: 15296 explicit Iterator(Document* aDoc, IteratorOption aOption) 15297 : mCurrent(PendingFullscreenChangeList::sList.getFirst()) { 15298 if (mCurrent) { 15299 if (aDoc->GetBrowsingContext()) { 15300 mRootBCForIteration = aDoc->GetBrowsingContext(); 15301 if (aOption == eDocumentsWithSameRoot) { 15302 BrowsingContext* bc = 15303 GetParentIgnoreChromeBoundary(mRootBCForIteration); 15304 while (bc) { 15305 mRootBCForIteration = bc; 15306 bc = GetParentIgnoreChromeBoundary(mRootBCForIteration); 15307 } 15308 } 15309 } 15310 SkipToNextMatch(); 15311 } 15312 } 15313 15314 UniquePtr<T> TakeAndNext() { 15315 auto thisChange = TakeAndNextInternal(); 15316 SkipToNextMatch(); 15317 return thisChange; 15318 } 15319 bool AtEnd() const { return mCurrent == nullptr; } 15320 15321 private: 15322 static BrowsingContext* GetParentIgnoreChromeBoundary( 15323 BrowsingContext* aBC) { 15324 // Chrome BrowsingContexts are only available in the parent process, so if 15325 // we're in a content process, we only worry about the context tree. 15326 if (XRE_IsParentProcess()) { 15327 return aBC->Canonical()->GetParentCrossChromeBoundary(); 15328 } 15329 return aBC->GetParent(); 15330 } 15331 15332 UniquePtr<T> TakeAndNextInternal() { 15333 FullscreenChange* thisChange = mCurrent; 15334 MOZ_ASSERT(thisChange->Type() == T::kType); 15335 mCurrent = mCurrent->removeAndGetNext(); 15336 return WrapUnique(static_cast<T*>(thisChange)); 15337 } 15338 void SkipToNextMatch() { 15339 while (mCurrent) { 15340 if (mCurrent->Type() == T::kType) { 15341 BrowsingContext* bc = mCurrent->Document()->GetBrowsingContext(); 15342 if (!bc) { 15343 // Always automatically drop fullscreen changes which are 15344 // from a document detached from the doc shell. 15345 UniquePtr<T> change = TakeAndNextInternal(); 15346 change->MayRejectPromise("Document is not active"); 15347 continue; 15348 } 15349 while (bc && bc != mRootBCForIteration) { 15350 bc = GetParentIgnoreChromeBoundary(bc); 15351 } 15352 if (bc) { 15353 break; 15354 } 15355 } 15356 // The current one either don't have matched type, or isn't 15357 // inside the given subtree, so skip this item. 15358 mCurrent = mCurrent->getNext(); 15359 } 15360 } 15361 15362 FullscreenChange* mCurrent; 15363 RefPtr<BrowsingContext> mRootBCForIteration; 15364 }; 15365 15366 private: 15367 static LinkedList<FullscreenChange> sList; 15368 }; 15369 15370 /* static */ 15371 MOZ_RUNINIT LinkedList<FullscreenChange> PendingFullscreenChangeList::sList; 15372 15373 size_t Document::CountFullscreenElements() const { 15374 size_t count = 0; 15375 for (const nsWeakPtr& ptr : mTopLayer) { 15376 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) { 15377 if (elem->State().HasState(ElementState::FULLSCREEN)) { 15378 count++; 15379 } 15380 } 15381 } 15382 return count; 15383 } 15384 15385 // https://github.com/whatwg/html/issues/9143 15386 // We need to consider the precedence between active modal dialog, topmost auto 15387 // popover and fullscreen element once it's specified. 15388 // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1859702 This can be 15389 // removed after CloseWatcher has shipped. 15390 void Document::HandleEscKey() { 15391 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) { 15392 nsCOMPtr<Element> element(do_QueryReferent(weakPtr)); 15393 if (RefPtr popoverHTMLEl = nsGenericHTMLElement::FromNodeOrNull(element)) { 15394 if (element->IsPopoverOpenedInMode(PopoverAttributeState::Auto)) { 15395 popoverHTMLEl->HidePopover(IgnoreErrors()); 15396 return; 15397 } 15398 } 15399 if (RefPtr dialogElement = HTMLDialogElement::FromNodeOrNull(element)) { 15400 if (StaticPrefs::dom_dialog_light_dismiss_enabled()) { 15401 if (dialogElement->GetClosedBy() != HTMLDialogElement::ClosedBy::None) { 15402 const mozilla::dom::Optional<nsAString> returnValue; 15403 dialogElement->RequestClose(returnValue); 15404 } 15405 } else { 15406 dialogElement->QueueCancelDialog(); 15407 } 15408 // If the dialog element's `closedby` attribute is "none", then this 15409 // means the dialog is effectively blocking the Esc key from 15410 // functioning. Returning without closing is the correct behaviour - as 15411 // this is the topmost element "handling" the esc key press. 15412 return; 15413 } 15414 } 15415 // Not all dialogs exist in the top layer, so despite already iterating 15416 // through all top layer elements we also need to check open dialogs that are 15417 // _not_ open via the top-layer (showModal). 15418 // The top-most dialog in mOpenDialogs may need to be closed. 15419 if (RefPtr<HTMLDialogElement> dialog = 15420 mOpenDialogs.SafeLastElement(nullptr)) { 15421 if (dialog->GetClosedBy() != HTMLDialogElement::ClosedBy::None) { 15422 MOZ_ASSERT(StaticPrefs::dom_dialog_light_dismiss_enabled(), 15423 "Light Dismiss must have been enabled for GetClosedBy() " 15424 "returns != ClosedBy::None"); 15425 const mozilla::dom::Optional<nsAString> returnValue; 15426 dialog->RequestClose(returnValue); 15427 } 15428 } 15429 } 15430 15431 MOZ_CAN_RUN_SCRIPT void Document::ProcessCloseRequest() { 15432 if (RefPtr win = GetInnerWindow()) { 15433 if (win->IsFullyActive()) { 15434 RefPtr manager = win->EnsureCloseWatcherManager(); 15435 manager->ProcessCloseRequest(); 15436 } 15437 } 15438 } 15439 15440 already_AddRefed<Promise> Document::ExitFullscreen(ErrorResult& aRv) { 15441 UniquePtr<FullscreenExit> exit = FullscreenExit::Create(this, aRv); 15442 RefPtr<Promise> promise = exit->GetPromise(); 15443 RestorePreviousFullscreenState(std::move(exit)); 15444 return promise.forget(); 15445 } 15446 15447 static void AskWindowToExitFullscreen(Document* aDoc) { 15448 if (XRE_GetProcessType() == GeckoProcessType_Content) { 15449 nsContentUtils::DispatchEventOnlyToChrome( 15450 aDoc, aDoc, u"MozDOMFullscreen:Exit"_ns, CanBubble::eYes, 15451 Cancelable::eNo, /* DefaultAction */ nullptr); 15452 } else { 15453 if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) { 15454 win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false); 15455 } 15456 } 15457 } 15458 15459 class nsCallExitFullscreen : public Runnable { 15460 public: 15461 explicit nsCallExitFullscreen(Document* aDoc) 15462 : mozilla::Runnable("nsCallExitFullscreen"), mDoc(aDoc) {} 15463 15464 NS_IMETHOD Run() final { 15465 if (!mDoc) { 15466 FullscreenRoots::ForEach(&AskWindowToExitFullscreen); 15467 } else { 15468 AskWindowToExitFullscreen(mDoc); 15469 } 15470 return NS_OK; 15471 } 15472 15473 private: 15474 nsCOMPtr<Document> mDoc; 15475 }; 15476 15477 /* static */ 15478 void Document::AsyncExitFullscreen(Document* aDoc) { 15479 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 15480 nsCOMPtr<nsIRunnable> exit = new nsCallExitFullscreen(aDoc); 15481 NS_DispatchToCurrentThread(exit.forget()); 15482 } 15483 15484 static uint32_t CountFullscreenSubDocuments(Document& aDoc) { 15485 uint32_t count = 0; 15486 // FIXME(emilio): Should this be recursive and dig into our nested subdocs? 15487 aDoc.EnumerateSubDocuments([&count](Document& aSubDoc) { 15488 if (aSubDoc.Fullscreen()) { 15489 count++; 15490 } 15491 return CallState::Continue; 15492 }); 15493 return count; 15494 } 15495 15496 bool Document::IsFullscreenLeaf() { 15497 // A fullscreen leaf document is fullscreen, and has no fullscreen 15498 // subdocuments. 15499 // 15500 // FIXME(emilio): This doesn't seem to account for fission iframes, is that 15501 // ok? 15502 return Fullscreen() && CountFullscreenSubDocuments(*this) == 0; 15503 } 15504 15505 static Document* GetFullscreenLeaf(Document& aDoc) { 15506 if (aDoc.IsFullscreenLeaf()) { 15507 return &aDoc; 15508 } 15509 if (!aDoc.Fullscreen()) { 15510 return nullptr; 15511 } 15512 Document* leaf = nullptr; 15513 aDoc.EnumerateSubDocuments([&leaf](Document& aSubDoc) { 15514 leaf = GetFullscreenLeaf(aSubDoc); 15515 return leaf ? CallState::Stop : CallState::Continue; 15516 }); 15517 return leaf; 15518 } 15519 15520 static Document* GetFullscreenLeaf(Document* aDoc) { 15521 if (Document* leaf = GetFullscreenLeaf(*aDoc)) { 15522 return leaf; 15523 } 15524 // Otherwise we could be either in a non-fullscreen doc tree, or we're 15525 // below the fullscreen doc. Start the search from the root. 15526 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(aDoc); 15527 return GetFullscreenLeaf(*root); 15528 } 15529 15530 static CallState ResetFullscreen(Document& aDocument) { 15531 if (Element* fsElement = aDocument.GetUnretargetedFullscreenElement()) { 15532 NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1, 15533 "Should have at most 1 fullscreen subdocument."); 15534 aDocument.CleanupFullscreenState(); 15535 NS_ASSERTION(!aDocument.Fullscreen(), "Should reset fullscreen"); 15536 DispatchFullscreenChange(aDocument, fsElement); 15537 aDocument.EnumerateSubDocuments(ResetFullscreen); 15538 } 15539 return CallState::Continue; 15540 } 15541 15542 // Since Document::ExitFullscreenInDocTree() could be called from 15543 // Element::UnbindFromTree() where it is not safe to synchronously run 15544 // script. This runnable is the script part of that function. 15545 class ExitFullscreenScriptRunnable : public Runnable { 15546 public: 15547 explicit ExitFullscreenScriptRunnable(Document* aRoot, Document* aLeaf) 15548 : mozilla::Runnable("ExitFullscreenScriptRunnable"), 15549 mRoot(aRoot), 15550 mLeaf(aLeaf) {} 15551 15552 NS_IMETHOD Run() override { 15553 // Dispatch MozDOMFullscreen:Exited to the original fullscreen leaf 15554 // document since we want this event to follow the same path that 15555 // MozDOMFullscreen:Entered was dispatched. 15556 nsContentUtils::DispatchEventOnlyToChrome( 15557 mLeaf, mLeaf, u"MozDOMFullscreen:Exited"_ns, CanBubble::eYes, 15558 Cancelable::eNo, /* DefaultAction */ nullptr); 15559 // Ensure the window exits fullscreen, as long as we don't have 15560 // pending fullscreen requests. 15561 if (nsPIDOMWindowOuter* win = mRoot->GetWindow()) { 15562 if (!mRoot->HasPendingFullscreenRequests()) { 15563 win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen, 15564 false); 15565 } 15566 } 15567 return NS_OK; 15568 } 15569 15570 private: 15571 nsCOMPtr<Document> mRoot; 15572 nsCOMPtr<Document> mLeaf; 15573 }; 15574 15575 /* static */ 15576 void Document::ExitFullscreenInDocTree(Document* aMaybeNotARootDoc) { 15577 MOZ_ASSERT(aMaybeNotARootDoc); 15578 15579 // Unlock the pointer 15580 PointerLockManager::Unlock("Document::ExitFullscreenInDocTree"); 15581 15582 // Resolve all promises which waiting for exit fullscreen. 15583 PendingFullscreenChangeList::Iterator<FullscreenExit> iter( 15584 aMaybeNotARootDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot); 15585 while (!iter.AtEnd()) { 15586 UniquePtr<FullscreenExit> exit = iter.TakeAndNext(); 15587 exit->MayResolvePromise(); 15588 } 15589 15590 nsCOMPtr<Document> root = aMaybeNotARootDoc->GetFullscreenRoot(); 15591 if (!root || !root->Fullscreen()) { 15592 // If a document was detached before exiting from fullscreen, it is 15593 // possible that the root had left fullscreen state. In this case, 15594 // we would not get anything from the ResetFullscreen() call. Root's 15595 // not being a fullscreen doc also means the widget should have 15596 // exited fullscreen state. It means even if we do not return here, 15597 // we would actually do nothing below except crashing ourselves via 15598 // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent 15599 // document. 15600 return; 15601 } 15602 15603 // Record the fullscreen leaf document for MozDOMFullscreen:Exited. 15604 // See ExitFullscreenScriptRunnable::Run for details. We have to 15605 // record it here because we don't have such information after we 15606 // reset the fullscreen state below. 15607 Document* fullscreenLeaf = GetFullscreenLeaf(root); 15608 15609 // Walk the tree of fullscreen documents, and reset their fullscreen state. 15610 ResetFullscreen(*root); 15611 15612 NS_ASSERTION(!root->Fullscreen(), 15613 "Fullscreen root should no longer be a fullscreen doc..."); 15614 15615 // Move the top-level window out of fullscreen mode. 15616 FullscreenRoots::Remove(root); 15617 15618 nsContentUtils::AddScriptRunner( 15619 new ExitFullscreenScriptRunnable(root, fullscreenLeaf)); 15620 } 15621 15622 static void DispatchFullscreenNewOriginEvent(Document* aDoc) { 15623 RefPtr<AsyncEventDispatcher> asyncDispatcher = 15624 new AsyncEventDispatcher(aDoc, u"MozDOMFullscreen:NewOrigin"_ns, 15625 CanBubble::eYes, ChromeOnlyDispatch::eYes); 15626 asyncDispatcher->PostDOMEvent(); 15627 } 15628 15629 void Document::RestorePreviousFullscreenState(UniquePtr<FullscreenExit> aExit) { 15630 NS_ASSERTION(!Fullscreen() || !FullscreenRoots::IsEmpty(), 15631 "Should have at least 1 fullscreen root when fullscreen!"); 15632 15633 if (!GetWindow()) { 15634 aExit->MayRejectPromise("No active window"); 15635 return; 15636 } 15637 if (!Fullscreen() || FullscreenRoots::IsEmpty()) { 15638 aExit->MayRejectPromise("Not in fullscreen mode"); 15639 return; 15640 } 15641 15642 nsCOMPtr<Document> fullScreenDoc = GetFullscreenLeaf(this); 15643 AutoTArray<Element*, 8> exitElements; 15644 15645 Document* doc = fullScreenDoc; 15646 // Collect all subdocuments. 15647 for (; doc != this; doc = doc->GetInProcessParentDocument()) { 15648 Element* fsElement = doc->GetUnretargetedFullscreenElement(); 15649 MOZ_ASSERT(fsElement, 15650 "Parent document of " 15651 "a fullscreen document without fullscreen element?"); 15652 exitElements.AppendElement(fsElement); 15653 } 15654 MOZ_ASSERT(doc == this, "Must have reached this doc"); 15655 // Collect all ancestor documents which we are going to change. 15656 for (; doc; doc = doc->GetInProcessParentDocument()) { 15657 Element* fsElement = doc->GetUnretargetedFullscreenElement(); 15658 MOZ_ASSERT(fsElement, 15659 "Ancestor of fullscreen document must also be in fullscreen"); 15660 if (doc != this) { 15661 if (auto* iframe = HTMLIFrameElement::FromNode(fsElement)) { 15662 if (iframe->FullscreenFlag()) { 15663 // If this is an iframe, and it explicitly requested 15664 // fullscreen, don't rollback it automatically. 15665 break; 15666 } 15667 } 15668 } 15669 exitElements.AppendElement(fsElement); 15670 if (doc->CountFullscreenElements() > 1) { 15671 break; 15672 } 15673 } 15674 15675 Document* lastDoc = exitElements.LastElement()->OwnerDoc(); 15676 size_t fullscreenCount = lastDoc->CountFullscreenElements(); 15677 if (!lastDoc->GetInProcessParentDocument() && fullscreenCount == 1) { 15678 // If we are fully exiting fullscreen, don't touch anything here, 15679 // just wait for the window to get out from fullscreen first. 15680 PendingFullscreenChangeList::Add(std::move(aExit)); 15681 AskWindowToExitFullscreen(this); 15682 return; 15683 } 15684 15685 // If fullscreen mode is updated the pointer should be unlocked 15686 PointerLockManager::Unlock("Document::RestorePreviousFullscreenState"); 15687 // All documents listed in the array except the last one are going to 15688 // completely exit from the fullscreen state. 15689 for (auto i : IntegerRange(exitElements.Length() - 1)) { 15690 exitElements[i]->OwnerDoc()->CleanupFullscreenState(); 15691 } 15692 // The last document will either rollback one fullscreen element, or 15693 // completely exit from the fullscreen state as well. 15694 Document* newFullscreenDoc; 15695 if (fullscreenCount > 1) { 15696 DebugOnly<bool> removedFullscreenElement = lastDoc->PopFullscreenElement(); 15697 MOZ_ASSERT(removedFullscreenElement); 15698 newFullscreenDoc = lastDoc; 15699 } else { 15700 lastDoc->CleanupFullscreenState(); 15701 newFullscreenDoc = lastDoc->GetInProcessParentDocument(); 15702 } 15703 // Dispatch the fullscreenchange event to all document listed. Note 15704 // that the loop order is reversed so that events are dispatched in 15705 // the tree order as indicated in the spec. 15706 for (Element* e : Reversed(exitElements)) { 15707 DispatchFullscreenChange(*e->OwnerDoc(), e); 15708 } 15709 aExit->MayResolvePromise(); 15710 15711 MOZ_ASSERT(newFullscreenDoc, 15712 "If we were going to exit from fullscreen on " 15713 "all documents in this doctree, we should've asked the window to " 15714 "exit first instead of reaching here."); 15715 if (fullScreenDoc != newFullscreenDoc && 15716 !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) { 15717 // We've popped so enough off the stack that we've rolled back to 15718 // a fullscreen element in a parent document. If this document is 15719 // cross origin, dispatch an event to chrome so it knows to show 15720 // the warning UI. 15721 DispatchFullscreenNewOriginEvent(newFullscreenDoc); 15722 } 15723 } 15724 15725 static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) { 15726 if (nsPresContext* presContext = aDoc->GetPresContext()) { 15727 presContext->UpdateViewportScrollStylesOverride(); 15728 } 15729 } 15730 15731 static void NotifyFullScreenChangedForMediaElement(Element& aElement) { 15732 // When a media element enters the fullscreen, we would like to notify that 15733 // to the media controller in order to update its status. 15734 if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) { 15735 mediaElem->NotifyFullScreenChanged(); 15736 } 15737 } 15738 15739 void Document::CleanupFullscreenState() { 15740 while (PopFullscreenElement(UpdateViewport::No)) { 15741 // Remove the next one if appropriate 15742 } 15743 15744 UpdateViewportScrollbarOverrideForFullscreen(this); 15745 mFullscreenRoot = nullptr; 15746 15747 // Restore the zoom level that was in place prior to entering fullscreen. 15748 if (PresShell* presShell = GetPresShell()) { 15749 if (presShell->GetMobileViewportManager()) { 15750 presShell->SetResolutionAndScaleTo( 15751 mSavedResolution, ResolutionChangeOrigin::MainThreadRestore); 15752 } 15753 } 15754 } 15755 15756 bool Document::PopFullscreenElement(UpdateViewport aUpdateViewport) { 15757 Element* removedElement = TopLayerPop([](Element* element) -> bool { 15758 return element->State().HasState(ElementState::FULLSCREEN); 15759 }); 15760 15761 if (!removedElement) { 15762 return false; 15763 } 15764 15765 MOZ_ASSERT(removedElement->State().HasState(ElementState::FULLSCREEN)); 15766 removedElement->RemoveStates(ElementState::FULLSCREEN | ElementState::MODAL); 15767 NotifyFullScreenChangedForMediaElement(*removedElement); 15768 // Reset iframe fullscreen flag. 15769 if (auto* iframe = HTMLIFrameElement::FromNode(removedElement)) { 15770 iframe->SetFullscreenFlag(false); 15771 } 15772 if (aUpdateViewport == UpdateViewport::Yes) { 15773 UpdateViewportScrollbarOverrideForFullscreen(this); 15774 } 15775 return true; 15776 } 15777 15778 void Document::SetFullscreenElement(Element& aElement) { 15779 ElementState statesToAdd = ElementState::FULLSCREEN; 15780 if (!IsInChromeDocShell()) { 15781 // Don't make the document modal in chrome documents, since we don't want 15782 // the browser UI like the context menu / etc to be inert. 15783 statesToAdd |= ElementState::MODAL; 15784 } 15785 aElement.AddStates(statesToAdd); 15786 TopLayerPush(aElement); 15787 NotifyFullScreenChangedForMediaElement(aElement); 15788 UpdateViewportScrollbarOverrideForFullscreen(this); 15789 } 15790 15791 void Document::TopLayerPush(Element& aElement) { 15792 const bool modal = aElement.State().HasState(ElementState::MODAL); 15793 15794 TopLayerPop(aElement); 15795 if (nsIFrame* f = aElement.GetPrimaryFrame()) { 15796 f->MarkNeedsDisplayItemRebuild(); 15797 } 15798 15799 mTopLayer.AppendElement(do_GetWeakReference(&aElement)); 15800 NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match"); 15801 15802 if (modal) { 15803 aElement.AddStates(ElementState::TOPMOST_MODAL); 15804 15805 bool foundExistingModalElement = false; 15806 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) { 15807 nsCOMPtr<Element> element(do_QueryReferent(weakPtr)); 15808 if (element && element != &aElement && 15809 element->State().HasState(ElementState::TOPMOST_MODAL)) { 15810 element->RemoveStates(ElementState::TOPMOST_MODAL); 15811 foundExistingModalElement = true; 15812 break; 15813 } 15814 } 15815 15816 if (!foundExistingModalElement) { 15817 Element* root = GetRootElement(); 15818 MOZ_RELEASE_ASSERT(root, "top layer element outside of document?"); 15819 if (&aElement != root) { 15820 // Add inert to the root element so that the inertness is applied to the 15821 // entire document. 15822 root->AddStates(ElementState::INERT); 15823 } 15824 } 15825 } 15826 } 15827 15828 void Document::AddModalDialog(HTMLDialogElement& aDialogElement) { 15829 aDialogElement.AddStates(ElementState::MODAL); 15830 TopLayerPush(aDialogElement); 15831 } 15832 15833 void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) { 15834 DebugOnly<Element*> removedElement = TopLayerPop(aDialogElement); 15835 MOZ_ASSERT(removedElement == &aDialogElement); 15836 aDialogElement.RemoveStates(ElementState::MODAL); 15837 } 15838 15839 void Document::AddOpenDialog(HTMLDialogElement& aElement) { 15840 MOZ_ASSERT(aElement.IsInComposedDoc(), 15841 "Disconnected Dialogs shouldn't go in Open Dialogs list"); 15842 MOZ_ASSERT(!mOpenDialogs.Contains(&aElement), 15843 "Dialog already in Open Dialogs list!"); 15844 mOpenDialogs.AppendElement(&aElement); 15845 } 15846 15847 void Document::RemoveOpenDialog(HTMLDialogElement& aElement) { 15848 mOpenDialogs.RemoveElement(&aElement); 15849 } 15850 15851 void Document::SetLastDialogPointerdownTarget(HTMLDialogElement& aElement) { 15852 mLastDialogPointerdownTarget = do_GetWeakReference(&aElement); 15853 } 15854 15855 HTMLDialogElement* Document::GetLastDialogPointerdownTarget() { 15856 nsCOMPtr<Element> element(do_QueryReferent(mLastDialogPointerdownTarget)); 15857 return HTMLDialogElement::FromNodeOrNull(element); 15858 } 15859 15860 bool Document::HasOpenDialogs() const { return !mOpenDialogs.IsEmpty(); } 15861 15862 HTMLDialogElement* Document::GetTopMostOpenDialog() { 15863 return mOpenDialogs.SafeLastElement(nullptr); 15864 } 15865 15866 bool Document::DialogIsInOpenDialogsList(HTMLDialogElement& aDialog) { 15867 return mOpenDialogs.Contains(&aDialog); 15868 } 15869 15870 Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) { 15871 if (mTopLayer.IsEmpty()) { 15872 return nullptr; 15873 } 15874 15875 // Remove the topmost element that qualifies aPredicate; This 15876 // is required is because the top layer contains not only 15877 // fullscreen elements, but also dialog elements. 15878 Element* removedElement = nullptr; 15879 for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) { 15880 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i])); 15881 if (element && aPredicate(element)) { 15882 removedElement = element; 15883 if (nsIFrame* f = element->GetPrimaryFrame()) { 15884 f->MarkNeedsDisplayItemRebuild(); 15885 } 15886 mTopLayer.RemoveElementAt(i); 15887 break; 15888 } 15889 } 15890 15891 // Pop from the stack null elements (references to elements which have 15892 // been GC'd since they were added to the stack) and elements which are 15893 // no longer in this document. 15894 // 15895 // FIXME(emilio): If this loop does something, it'd violate the assertions 15896 // from GetTopLayerTop()... What gives? 15897 while (!mTopLayer.IsEmpty()) { 15898 Element* element = GetTopLayerTop(); 15899 if (!element || element->GetComposedDoc() != this) { 15900 if (element) { 15901 if (nsIFrame* f = element->GetPrimaryFrame()) { 15902 f->MarkNeedsDisplayItemRebuild(); 15903 } 15904 } 15905 15906 mTopLayer.RemoveLastElement(); 15907 } else { 15908 // The top element of the stack is now an in-doc element. Return here. 15909 break; 15910 } 15911 } 15912 15913 if (!removedElement) { 15914 return nullptr; 15915 } 15916 15917 const bool modal = removedElement->State().HasState(ElementState::MODAL); 15918 15919 if (modal) { 15920 removedElement->RemoveStates(ElementState::TOPMOST_MODAL); 15921 bool foundExistingModalElement = false; 15922 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) { 15923 nsCOMPtr<Element> element(do_QueryReferent(weakPtr)); 15924 if (element && element->State().HasState(ElementState::MODAL)) { 15925 element->AddStates(ElementState::TOPMOST_MODAL); 15926 foundExistingModalElement = true; 15927 break; 15928 } 15929 } 15930 // No more modal elements, make the document not inert anymore. 15931 if (!foundExistingModalElement) { 15932 Element* root = GetRootElement(); 15933 if (root && !root->GetBoolAttr(nsGkAtoms::inert)) { 15934 root->RemoveStates(ElementState::INERT); 15935 } 15936 } 15937 } 15938 15939 return removedElement; 15940 } 15941 15942 Element* Document::TopLayerPop(Element& aElement) { 15943 auto predictFunc = [&aElement](Element* element) { 15944 return element == &aElement; 15945 }; 15946 return TopLayerPop(predictFunc); 15947 } 15948 15949 void Document::GetWireframe(bool aIncludeNodes, 15950 Nullable<Wireframe>& aWireframe) { 15951 FlushPendingNotifications(FlushType::Layout); 15952 GetWireframeWithoutFlushing(aIncludeNodes, aWireframe); 15953 } 15954 15955 void Document::GetWireframeWithoutFlushing(bool aIncludeNodes, 15956 Nullable<Wireframe>& aWireframe) { 15957 using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions; 15958 using FrameForPointOption = nsLayoutUtils::FrameForPointOption; 15959 15960 PresShell* shell = GetPresShell(); 15961 if (!shell) { 15962 return; 15963 } 15964 15965 nsPresContext* pc = shell->GetPresContext(); 15966 if (!pc) { 15967 return; 15968 } 15969 15970 nsIFrame* rootFrame = shell->GetRootFrame(); 15971 if (!rootFrame) { 15972 return; 15973 } 15974 15975 auto& wireframe = aWireframe.SetValue(); 15976 wireframe.mCanvasBackground = 15977 shell->ComputeCanvasBackground().mViewport.mColor; 15978 15979 FrameForPointOptions options; 15980 options.mBits += FrameForPointOption::IgnoreCrossDoc; 15981 options.mBits += FrameForPointOption::IgnorePaintSuppression; 15982 options.mBits += FrameForPointOption::OnlyVisible; 15983 15984 AutoTArray<nsIFrame*, 32> frames; 15985 const RelativeTo relativeTo{rootFrame, mozilla::ViewportType::Layout}; 15986 nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames, 15987 options); 15988 15989 // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or 15990 // something perhaps, but seems hard / like it'd involve at least some extra 15991 // copying around, since they don't outlive GetFramesForArea. 15992 auto& rects = wireframe.mRects.Construct(); 15993 if (!rects.SetCapacity(frames.Length(), fallible)) { 15994 return; 15995 } 15996 for (nsIFrame* frame : Reversed(frames)) { 15997 auto [rectColor, 15998 rectType] = [&]() -> std::tuple<nscolor, WireframeRectType> { 15999 if (frame->IsTextFrame()) { 16000 return {frame->StyleText()->mWebkitTextFillColor.CalcColor(frame), 16001 WireframeRectType::Text}; 16002 } 16003 if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) { 16004 return {0, WireframeRectType::Image}; 16005 } 16006 if (frame->IsThemed()) { 16007 return {0, WireframeRectType::Background}; 16008 } 16009 bool drawImage = false; 16010 bool drawColor = false; 16011 if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) { 16012 const nscolor color = nsCSSRendering::DetermineBackgroundColor( 16013 pc, bgStyle, frame, drawImage, drawColor); 16014 if (drawImage && 16015 !bgStyle->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) { 16016 return {color, WireframeRectType::Image}; 16017 } 16018 if (drawColor && !frame->IsCanvasFrame()) { 16019 // Canvas frame background already accounted for in mCanvasBackground. 16020 return {color, WireframeRectType::Background}; 16021 } 16022 } 16023 return {0, WireframeRectType::Unknown}; 16024 }(); 16025 16026 if (rectType == WireframeRectType::Unknown) { 16027 continue; 16028 } 16029 16030 const auto r = 16031 CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor( 16032 frame, frame->GetRectRelativeToSelf(), relativeTo)); 16033 if ((uint32_t)r.Area() < 16034 StaticPrefs::browser_history_wireframeAreaThreshold()) { 16035 continue; 16036 } 16037 16038 // Can't really fail because SetCapacity succeeded. 16039 auto& taggedRect = *rects.AppendElement(fallible); 16040 16041 if (aIncludeNodes) { 16042 if (nsIContent* c = frame->GetContent()) { 16043 taggedRect.mNode.Construct(c); 16044 } 16045 } 16046 taggedRect.mX = r.x; 16047 taggedRect.mY = r.y; 16048 taggedRect.mWidth = r.width; 16049 taggedRect.mHeight = r.height; 16050 taggedRect.mColor = rectColor; 16051 taggedRect.mType.Construct(rectType); 16052 } 16053 } 16054 16055 Element* Document::GetTopLayerTop() { 16056 if (mTopLayer.IsEmpty()) { 16057 return nullptr; 16058 } 16059 uint32_t last = mTopLayer.Length() - 1; 16060 nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last])); 16061 NS_ASSERTION(element, "Should have a top layer element!"); 16062 NS_ASSERTION(element->IsInComposedDoc(), 16063 "Top layer element should be in doc"); 16064 NS_ASSERTION(element->OwnerDoc() == this, 16065 "Top layer element should be in this doc"); 16066 return element; 16067 } 16068 16069 Element* Document::GetUnretargetedFullscreenElement() const { 16070 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) { 16071 nsCOMPtr<Element> element(do_QueryReferent(weakPtr)); 16072 // Per spec, the fullscreen element is the topmost element in the document’s 16073 // top layer whose fullscreen flag is set, if any, and null otherwise. 16074 if (element && element->State().HasState(ElementState::FULLSCREEN)) { 16075 return element; 16076 } 16077 } 16078 return nullptr; 16079 } 16080 16081 nsTArray<Element*> Document::GetTopLayer() const { 16082 nsTArray<Element*> elements; 16083 for (const nsWeakPtr& ptr : mTopLayer) { 16084 if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) { 16085 elements.AppendElement(elem); 16086 } 16087 } 16088 return elements; 16089 } 16090 16091 bool Document::TopLayerContains(Element& aElement) const { 16092 if (mTopLayer.IsEmpty()) { 16093 return false; 16094 } 16095 nsWeakPtr weakElement = do_GetWeakReference(&aElement); 16096 return mTopLayer.Contains(weakElement); 16097 } 16098 16099 // https://html.spec.whatwg.org/#close-entire-popover-list 16100 void Document::CloseEntirePopoverList(PopoverAttributeState aMode, 16101 bool aFocusPreviousElement, 16102 bool aFireEvents) { 16103 // 1. While popoverList is not empty: 16104 // XXX: Rather than computing the list, find from top layer elements 16105 while (RefPtr popover = GetTopmostPopoverOf(aMode)) { 16106 // 1.1. Run the hide popover algorithm given popoverList's last item, 16107 // focusPreviousElement, fireEvents, false, and null. 16108 HidePopover(*popover, aFocusPreviousElement, aFireEvents, 16109 /* aSource */ nullptr, IgnoreErrors()); 16110 } 16111 } 16112 16113 // https://html.spec.whatwg.org/#hide-all-popovers-until 16114 void Document::HideAllPopoversUntil(nsINode& aEndpoint, 16115 bool aFocusPreviousElement, 16116 bool aFireEvents) { 16117 const auto* endpointHTMLEl = nsGenericHTMLElement::FromNodeOrNull(&aEndpoint); 16118 16119 // 1. If endpoint is an HTML element and endpoint is not in the popover 16120 // showing state, then return. 16121 if (endpointHTMLEl && !endpointHTMLEl->IsPopoverOpen()) { 16122 return; 16123 } 16124 16125 // 2. Let document be endpoint's node document. 16126 MOZ_ASSERT(aEndpoint.OwnerDoc() == this); 16127 // 3. Assert: endpoint is a Document or endpoint's popover visibility state is 16128 // showing. 16129 // 4. Assert: endpoint is a Document or endpoint's popover attribute is in the 16130 // Auto state or endpoint's popover attribute is in the Hint state. 16131 // todo(keithamus): Implement this 16132 16133 // 5. If endpoint is a Document: 16134 if (&aEndpoint == this) { 16135 // 5.1. Run close entire popover list given document's showing hint popover 16136 // list, focusPreviousElement, and fireEvents. 16137 // 5.2. Run close entire popover list given document's showing auto popover 16138 // list, focusPreviousElement, and fireEvents. 16139 CloseEntirePopoverList(PopoverAttributeState::Auto, aFocusPreviousElement, 16140 aFireEvents); 16141 // 5.3. Return. 16142 return; 16143 } 16144 16145 // 6. If document's showing hint popover list contains endpoint: 16146 // 6.1. Assert: endpoint's popover attribute is in the Hint state. 16147 // 6.2. Run hide popover stack until given endpoint, document's showing hint 16148 // popover list, focusPreviousElement, and fireEvents. 16149 // 6.3. Return. 16150 // todo(keithamus): Implement this 16151 16152 // 7. Run close entire popover list given document's showing hint popover 16153 // list, focusPreviousElement, and fireEvents. 16154 // 8. If document's showing auto popover list does not contain endpoint, then 16155 // return. 16156 16157 // 9. Run hide popover stack until given endpoint, document's showing auto 16158 // popover list, focusPreviousElement, and fireEvents. 16159 HidePopoverStackUntil(PopoverAttributeState::Auto, aEndpoint, 16160 aFocusPreviousElement, aFireEvents); 16161 } 16162 16163 // https://html.spec.whatwg.org/#hide-popover-stack-until 16164 void Document::HidePopoverStackUntil(PopoverAttributeState aMode, 16165 nsINode& aEndpoint, 16166 bool aFocusPreviousElement, 16167 bool aFireEvents) { 16168 auto needRepeatingHide = [&]() { 16169 auto autoList = PopoverListOf(aMode); 16170 return autoList.Contains(&aEndpoint) && 16171 &aEndpoint != autoList.LastElement(); 16172 }; 16173 16174 // 1. Let repeatingHide be false. 16175 bool repeatingHide = false; 16176 bool fireEvents = aFireEvents; 16177 16178 // 2. Perform the following steps at least once: 16179 do { 16180 // 2.1. Let lastToHide be null. 16181 RefPtr<const Element> lastToHide = nullptr; 16182 bool foundEndpoint = false; 16183 // 2.2. For each popover in popoverList: 16184 for (const Element* popover : PopoverListOf(aMode)) { 16185 // 2.2.1. If popover is endpoint, then break. 16186 // todo(keithamus): Get this logic closer to spec. 16187 if (popover == &aEndpoint) { 16188 foundEndpoint = true; 16189 } else if (foundEndpoint) { 16190 // 2.2.2. Set lastToHide to popover. 16191 lastToHide = popover; 16192 break; 16193 } 16194 } 16195 16196 // 2.3. If lastToHide is null, then return. 16197 if (!foundEndpoint) { 16198 CloseEntirePopoverList(PopoverAttributeState::Auto, aFocusPreviousElement, 16199 fireEvents); 16200 return; 16201 } 16202 16203 // 2.4. While lastToHide's popover visibility state is showing: 16204 while (lastToHide && lastToHide->IsPopoverOpen()) { 16205 // 2.4.1. Assert: popoverList is not empty. 16206 // todo(keithamus): Assert 16207 16208 // 2.4.2. Run the hide popover algorithm given the last item in 16209 // popoverList, focusPreviousElement, fireEvents, false, and null. 16210 RefPtr<Element> topmost = 16211 GetTopmostPopoverOf(PopoverAttributeState::Auto); 16212 if (!topmost) { 16213 break; 16214 } 16215 HidePopover(*topmost, aFocusPreviousElement, fireEvents, 16216 /* aSource */ nullptr, IgnoreErrors()); 16217 } 16218 16219 // 2.5. Assert: repeatingHide is false or popoverList's last item is 16220 // endpoint. 16221 // todo(keithamus): Assert 16222 16223 // 2.6. Set repeatingHide to true if popoverList contains endpoint and 16224 // popoverList's last item is not endpoint, otherwise false. 16225 repeatingHide = needRepeatingHide(); 16226 // 2.7. If repeatingHide is true, then set fireEvents to false. 16227 if (repeatingHide) { 16228 fireEvents = false; 16229 } 16230 // ... and keep performing them while repeatingHide is true. 16231 } while (repeatingHide); 16232 } 16233 16234 // https://html.spec.whatwg.org/#hide-popover-algorithm 16235 void Document::HidePopover(Element& aPopover, bool aFocusPreviousElement, 16236 bool aFireEvents, Element* aSource, 16237 ErrorResult& aRv) { 16238 RefPtr<nsGenericHTMLElement> popoverHTMLEl = 16239 nsGenericHTMLElement::FromNode(aPopover); 16240 NS_ASSERTION(popoverHTMLEl, "Not a HTML element"); 16241 16242 // 1. If the result of running check popover validity given element, true, 16243 // throwExceptions, and null is false, then return. 16244 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing, 16245 nullptr, aRv)) { 16246 return; 16247 } 16248 16249 // 2. Let document be element's node document. 16250 16251 // 3. Let nestedHide be element's popover showing or hiding. 16252 bool wasShowingOrHiding = 16253 popoverHTMLEl->GetPopoverData()->IsShowingOrHiding(); 16254 16255 // 4. Set element's popover showing or hiding to true. 16256 popoverHTMLEl->GetPopoverData()->SetIsShowingOrHiding(true); 16257 16258 // 5. If nestedHide is true, then set fireEvents to false. 16259 const bool fireEvents = aFireEvents && !wasShowingOrHiding; 16260 16261 // 6. Let cleanupSteps be the following steps: 16262 auto cleanupHidingFlag = MakeScopeExit([&]() { 16263 if (auto* popoverData = popoverHTMLEl->GetPopoverData()) { 16264 // 6.1. If nestedHide is false, then set element's popover showing or 16265 // hiding to false. 16266 popoverData->SetIsShowingOrHiding(wasShowingOrHiding); 16267 // 6.2. If element's popover close watcher is not null, then: 16268 // 6.2.1. Destroy element's popover close watcher. 16269 // 6.2.2. Set element's popover close watcher to null. 16270 popoverData->DestroyCloseWatcher(); 16271 } 16272 }); 16273 16274 PopoverData* popoverData = popoverHTMLEl->GetPopoverData(); 16275 16276 // 7. If element's opened in popover mode is "auto" or "hint", then: 16277 if (popoverData && 16278 popoverData->GetOpenedInMode() == PopoverAttributeState::Auto) { 16279 // 7.1. Run hide all popovers until given element, focusPreviousElement, and 16280 // fireEvents. 16281 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, fireEvents); 16282 16283 // 7.2. If the result of running check popover validity given element, true, 16284 // and throwExceptions is false, then run cleanupSteps and return. 16285 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing, 16286 nullptr, aRv)) { 16287 return; 16288 } 16289 16290 // TODO: we can't always guarantee: 16291 // The last item in document's auto popover list is popoverHTMLEl. 16292 // See, https://github.com/whatwg/html/issues/9197 16293 // If popoverHTMLEl is not on top, hide popovers again without firing 16294 // events. 16295 if (NS_WARN_IF(GetTopmostPopoverOf(PopoverAttributeState::Auto) != 16296 popoverHTMLEl)) { 16297 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false); 16298 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing, 16299 nullptr, aRv)) { 16300 return; 16301 } 16302 MOZ_ASSERT( 16303 GetTopmostPopoverOf(PopoverAttributeState::Auto) == popoverHTMLEl, 16304 "popoverHTMLEl should be on top of auto popover list"); 16305 } 16306 } 16307 16308 auto* data = popoverHTMLEl->GetPopoverData(); 16309 MOZ_ASSERT(data, "Should have popover data"); 16310 16311 // 9. If fireEvents is true: 16312 // Fire beforetoggle event and re-check popover validity. 16313 if (fireEvents) { 16314 // 9.1. Fire an event named beforetoggle, using ToggleEvent, with the 16315 // oldState attribute initialized to "open" and the newState attribute 16316 // initialized to "closed" at element. Intentionally ignore the return value 16317 // here as only on open event for beforetoggle the cancelable attribute is 16318 // initialized to true. 16319 popoverHTMLEl->FireToggleEvent(u"open"_ns, u"closed"_ns, u"beforetoggle"_ns, 16320 aSource); 16321 16322 // 9.2. If autoPopoverListContainsElement is true and document's showing 16323 // auto popover list's last item is not element, then run hide all popovers 16324 // until given element, focusPreviousElement, and false. Hide all popovers 16325 // when beforetoggle shows a popover. 16326 if (popoverHTMLEl->IsPopoverOpenedInMode(PopoverAttributeState::Auto) && 16327 GetTopmostPopoverOf(PopoverAttributeState::Auto) != popoverHTMLEl) { 16328 HideAllPopoversUntil(*popoverHTMLEl, aFocusPreviousElement, false); 16329 } 16330 16331 // 9.3. If the result of running check popover validity given element, true, 16332 // throwExceptions, and null is false, then run cleanupSteps and return. 16333 if (!popoverHTMLEl->CheckPopoverValidity(PopoverVisibilityState::Showing, 16334 nullptr, aRv)) { 16335 return; 16336 } 16337 16338 // 9.4. XXX: See below 16339 16340 // 9.5. Set element's implicit anchor element to null. 16341 data->SetInvoker(nullptr); 16342 } 16343 16344 // 9.4. Request an element to be removed from the top layer given element. 16345 // 10. Otherwise, remove an element from the top layer immediately given 16346 // element. 16347 RemovePopoverFromTopLayer(aPopover); 16348 16349 // 11. Set element's popover invoker to null. 16350 data->SetInvoker(nullptr); 16351 16352 // 12. Set element's opened in popover mode to null. 16353 popoverHTMLEl->GetPopoverData()->SetOpenedInMode(PopoverAttributeState::None); 16354 16355 // 13. Set element's popover visibility state to hidden. 16356 popoverHTMLEl->PopoverPseudoStateUpdate(false, true); 16357 popoverHTMLEl->GetPopoverData()->SetPopoverVisibilityState( 16358 PopoverVisibilityState::Hidden); 16359 16360 // 14. If fireEvents is true, then queue a popover toggle event task given 16361 // element, "open", and "closed". Queue popover toggle event task. 16362 if (fireEvents) { 16363 popoverHTMLEl->QueuePopoverEventTask(PopoverVisibilityState::Showing, 16364 aSource); 16365 } 16366 16367 // 15. Let previouslyFocusedElement be element's previously focused element. 16368 // 16. If previouslyFocusedElement is not null, then: 16369 if (aFocusPreviousElement) { 16370 // 16.1. Set element's previously focused element to null. 16371 // 16.2. If focusPreviousElement is true and document's focused area of the 16372 // document's DOM anchor is a shadow-including inclusive descendant of 16373 // element, then run the focusing steps for previouslyFocusedElement; the 16374 // viewport should not be scrolled by doing this step. 16375 popoverHTMLEl->FocusPreviousElementAfterHidingPopover(); 16376 } else { 16377 popoverHTMLEl->ForgetPreviouslyFocusedElementAfterHidingPopover(); 16378 } 16379 } 16380 16381 nsTArray<Element*> Document::PopoverListOf(PopoverAttributeState aMode) const { 16382 nsTArray<Element*> elements; 16383 for (const nsWeakPtr& ptr : mTopLayer) { 16384 if (nsCOMPtr<Element> element = do_QueryReferent(ptr)) { 16385 if (element && element->IsPopoverOpenedInMode(aMode)) { 16386 elements.AppendElement(element); 16387 } 16388 } 16389 } 16390 return elements; 16391 } 16392 16393 Element* Document::GetTopmostPopoverOf(PopoverAttributeState aMode) const { 16394 for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) { 16395 nsCOMPtr<Element> element(do_QueryReferent(weakPtr)); 16396 if (element && element->IsPopoverOpenedInMode(aMode)) { 16397 return element; 16398 } 16399 } 16400 return nullptr; 16401 } 16402 16403 void Document::AddPopoverToTopLayer(Element& aElement) { 16404 MOZ_ASSERT(aElement.GetPopoverData()); 16405 TopLayerPush(aElement); 16406 } 16407 16408 void Document::RemovePopoverFromTopLayer(Element& aElement) { 16409 MOZ_ASSERT(aElement.GetPopoverData()); 16410 TopLayerPop(aElement); 16411 } 16412 16413 // Returns true if aDoc browsing context is focused. 16414 bool IsInFocusedTab(Document* aDoc) { 16415 BrowsingContext* bc = aDoc->GetBrowsingContext(); 16416 if (!bc) { 16417 return false; 16418 } 16419 16420 nsFocusManager* fm = nsFocusManager::GetFocusManager(); 16421 if (!fm) { 16422 return false; 16423 } 16424 16425 if (XRE_IsParentProcess()) { 16426 // Keep dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xhtml happy 16427 // by retaining the old code path for the parent process. 16428 nsIDocShell* docshell = aDoc->GetDocShell(); 16429 if (!docshell) { 16430 return false; 16431 } 16432 nsCOMPtr<nsIDocShellTreeItem> rootItem; 16433 docshell->GetInProcessRootTreeItem(getter_AddRefs(rootItem)); 16434 if (!rootItem) { 16435 return false; 16436 } 16437 nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow(); 16438 if (!rootWin) { 16439 return false; 16440 } 16441 16442 nsCOMPtr<nsPIDOMWindowOuter> activeWindow; 16443 activeWindow = fm->GetActiveWindow(); 16444 if (!activeWindow) { 16445 return false; 16446 } 16447 16448 return activeWindow == rootWin; 16449 } 16450 16451 return fm->GetActiveBrowsingContext() == bc->Top(); 16452 } 16453 16454 // Returns true if aDoc browsing context is focused and is also active. 16455 bool IsInActiveTab(Document* aDoc) { 16456 if (!IsInFocusedTab(aDoc)) { 16457 return false; 16458 } 16459 16460 BrowsingContext* bc = aDoc->GetBrowsingContext(); 16461 MOZ_ASSERT(bc, "With no BrowsingContext, we should have failed earlier."); 16462 return bc->IsActive(); 16463 } 16464 16465 void Document::RemoteFrameFullscreenChanged(Element* aFrameElement) { 16466 // Ensure the frame element is the fullscreen element in this document. 16467 // If the frame element is already the fullscreen element in this document, 16468 // this has no effect. 16469 auto request = FullscreenRequest::CreateForRemote(aFrameElement); 16470 RequestFullscreen(std::move(request), XRE_IsContentProcess()); 16471 } 16472 16473 void Document::RemoteFrameFullscreenReverted() { 16474 UniquePtr<FullscreenExit> exit = FullscreenExit::CreateForRemote(this); 16475 RestorePreviousFullscreenState(std::move(exit)); 16476 } 16477 16478 static bool HasFullscreenSubDocument(Document& aDoc) { 16479 uint32_t count = CountFullscreenSubDocuments(aDoc); 16480 NS_ASSERTION(count <= 1, 16481 "Fullscreen docs should have at most 1 fullscreen child!"); 16482 return count >= 1; 16483 } 16484 16485 // Returns nullptr if a request for Fullscreen API is currently enabled 16486 // in the given document. Returns a static string indicates the reason 16487 // why it is not enabled otherwise. 16488 const char* Document::GetFullscreenError(CallerType aCallerType) { 16489 if (!StaticPrefs::full_screen_api_enabled()) { 16490 return "FullscreenDeniedDisabled"; 16491 } 16492 16493 BrowsingContext* bc = GetBrowsingContext(); 16494 // https://github.com/WICG/document-picture-in-picture/issues/133 16495 if (!bc || bc->Top()->GetIsDocumentPiP()) { 16496 return "FullscreenDeniedPiP"; 16497 } 16498 16499 if (aCallerType == CallerType::System) { 16500 // Chrome code can always use the fullscreen API, provided it's not 16501 // explicitly disabled. 16502 return nullptr; 16503 } 16504 16505 if (!IsVisible()) { 16506 return "FullscreenDeniedHidden"; 16507 } 16508 16509 if (!FeaturePolicyUtils::IsFeatureAllowed(this, u"fullscreen"_ns)) { 16510 return "FullscreenDeniedFeaturePolicy"; 16511 } 16512 16513 // Ensure that all containing elements are <iframe> and have allowfullscreen 16514 // attribute set. 16515 if (!bc->FullscreenAllowed()) { 16516 return "FullscreenDeniedContainerNotAllowed"; 16517 } 16518 16519 return nullptr; 16520 } 16521 16522 // Informs JSWA Fullscreen implementation to resume via sending 16523 // "MozDOMFullscreen:Entered". 16524 static inline void PropagateFullscreenRequest(Document* aDoc, 16525 Element* aElement) { 16526 nsContentUtils::DispatchEventOnlyToChrome( 16527 aDoc, aElement, u"MozDOMFullscreen:Entered"_ns, CanBubble::eYes, 16528 Cancelable::eNo, /* DefaultAction */ nullptr); 16529 } 16530 16531 static bool ElementIsRemoteFrame(Element* aElement) { 16532 MOZ_ASSERT(aElement); 16533 RefPtr<nsFrameLoader> loader; 16534 if (RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(aElement)) { 16535 loader = loaderOwner->GetFrameLoader(); 16536 } 16537 return loader && loader->IsRemoteFrame(); 16538 } 16539 16540 bool Document::FullscreenElementReadyCheck(FullscreenRequest& aRequest) { 16541 Element* elem = aRequest.Element(); 16542 // Strictly speaking, this isn't part of the fullscreen element ready 16543 // check in the spec, but per steps in the spec, when an element which 16544 // is already the fullscreen element requests fullscreen, nothing 16545 // should change and no event should be dispatched, but we still need 16546 // to resolve the returned promise. 16547 Element* fullscreenElement = GetUnretargetedFullscreenElement(); 16548 if (NS_WARN_IF(elem == fullscreenElement)) { 16549 // But this introduces behavior that we now need to account for; 16550 // because we can have arbitrary depth of OOP-frames, we may hit this check 16551 // for a process that already is fullscreen, e.g. the parent process. 16552 // If the target element is a frame or we're the parent process, just resume 16553 // the JS Window Actor messaging without doing any more work. 16554 // We know for sure, that the document must be fullscreened already, so 16555 // there is no request to the OS for fullscreen that needs to be made, for 16556 // instance. Note: this is just for JSWA not the platform-only fullscreen 16557 // implementation. 16558 if (ElementIsRemoteFrame(elem)) { 16559 PropagateFullscreenRequest(this, elem); 16560 } 16561 aRequest.MayResolvePromise(); 16562 return false; 16563 } 16564 if (!elem->IsInComposedDoc()) { 16565 aRequest.Reject("FullscreenDeniedNotInDocument"); 16566 return false; 16567 } 16568 if (elem->IsPopoverOpen()) { 16569 aRequest.Reject("FullscreenDeniedPopoverOpen"); 16570 return false; 16571 } 16572 if (elem->OwnerDoc() != this) { 16573 aRequest.Reject("FullscreenDeniedMovedDocument"); 16574 return false; 16575 } 16576 if (!GetWindow()) { 16577 aRequest.Reject("FullscreenDeniedLostWindow"); 16578 return false; 16579 } 16580 if (const char* msg = GetFullscreenError(aRequest.mCallerType)) { 16581 aRequest.Reject(msg); 16582 return false; 16583 } 16584 if (HasFullscreenSubDocument(*this)) { 16585 aRequest.Reject("FullscreenDeniedSubDocFullScreen"); 16586 return false; 16587 } 16588 if (elem->IsHTMLElement(nsGkAtoms::dialog)) { 16589 aRequest.Reject("FullscreenDeniedHTMLDialog"); 16590 return false; 16591 } 16592 if (!nsContentUtils::IsChromeDoc(this) && !IsInFocusedTab(this)) { 16593 aRequest.Reject("FullscreenDeniedNotFocusedTab"); 16594 return false; 16595 } 16596 return true; 16597 } 16598 16599 static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(Document* aDoc) { 16600 MOZ_ASSERT(XRE_IsParentProcess()); 16601 nsIDocShell* docShell = aDoc->GetDocShell(); 16602 if (!docShell) { 16603 return nullptr; 16604 } 16605 nsCOMPtr<nsIDocShellTreeItem> rootItem; 16606 docShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem)); 16607 return rootItem ? rootItem->GetWindow() : nullptr; 16608 } 16609 16610 static bool ShouldApplyFullscreenDirectly(Document* aDoc, 16611 nsPIDOMWindowOuter* aRootWin) { 16612 MOZ_ASSERT(XRE_IsParentProcess()); 16613 // If we are in the chrome process, and the window has not been in 16614 // fullscreen, we certainly need to make that fullscreen first. 16615 if (!aRootWin->GetFullScreen()) { 16616 return false; 16617 } 16618 // The iterator not being at end indicates there is still some 16619 // pending fullscreen request relates to this document. We have to 16620 // push the request to the pending queue so requests are handled 16621 // in the correct order. 16622 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter( 16623 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot); 16624 if (!iter.AtEnd()) { 16625 return false; 16626 } 16627 16628 // Same thing for exits. If we have any pending, we have to push 16629 // to the pending queue. 16630 PendingFullscreenChangeList::Iterator<FullscreenExit> iterExit( 16631 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot); 16632 if (!iterExit.AtEnd()) { 16633 return false; 16634 } 16635 16636 // We have to apply the fullscreen state directly in this case, 16637 // because nsGlobalWindow::SetFullscreenInternal() will do nothing 16638 // if it is already in fullscreen. If we do not apply the state but 16639 // instead add it to the queue and wait for the window as normal, 16640 // we would get stuck. 16641 return true; 16642 } 16643 16644 static bool CheckFullscreenAllowedElementType(const Element* elem) { 16645 // Per spec only HTML, <svg>, and <math> should be allowed, but 16646 // we also need to allow XUL elements right now. 16647 return elem->IsHTMLElement() || elem->IsXULElement() || 16648 elem->IsSVGElement(nsGkAtoms::svg) || 16649 elem->IsMathMLElement(nsGkAtoms::math); 16650 } 16651 16652 void Document::RequestFullscreen(UniquePtr<FullscreenRequest> aRequest, 16653 bool aApplyFullscreenDirectly) { 16654 if (XRE_IsContentProcess()) { 16655 RequestFullscreenInContentProcess(std::move(aRequest), 16656 aApplyFullscreenDirectly); 16657 } else { 16658 RequestFullscreenInParentProcess(std::move(aRequest), 16659 aApplyFullscreenDirectly); 16660 } 16661 } 16662 16663 void Document::RequestFullscreenInContentProcess( 16664 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) { 16665 MOZ_ASSERT(XRE_IsContentProcess()); 16666 16667 // If we are in the content process, we can apply the fullscreen 16668 // state directly only if we have been in DOM fullscreen, because 16669 // otherwise we always need to notify the chrome. 16670 if (aApplyFullscreenDirectly || 16671 nsContentUtils::GetInProcessSubtreeRootDocument(this)->Fullscreen()) { 16672 ApplyFullscreen(std::move(aRequest)); 16673 return; 16674 } 16675 16676 if (!CheckFullscreenAllowedElementType(aRequest->Element())) { 16677 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML"); 16678 return; 16679 } 16680 16681 // We don't need to check element ready before this point, because 16682 // if we called ApplyFullscreen, it would check that for us. 16683 if (!FullscreenElementReadyCheck(*aRequest)) { 16684 return; 16685 } 16686 16687 PendingFullscreenChangeList::Add(std::move(aRequest)); 16688 // If we are not the top level process, dispatch an event to make 16689 // our parent process go fullscreen first. 16690 Dispatch(NS_NewRunnableFunction( 16691 "Document::RequestFullscreenInContentProcess", [self = RefPtr{this}] { 16692 if (!self->HasPendingFullscreenRequests()) { 16693 return; 16694 } 16695 nsContentUtils::DispatchEventOnlyToChrome( 16696 self, self, u"MozDOMFullscreen:Request"_ns, CanBubble::eYes, 16697 Cancelable::eNo, /* DefaultAction */ nullptr); 16698 })); 16699 } 16700 16701 void Document::RequestFullscreenInParentProcess( 16702 UniquePtr<FullscreenRequest> aRequest, bool aApplyFullscreenDirectly) { 16703 MOZ_ASSERT(XRE_IsParentProcess()); 16704 nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this); 16705 if (!rootWin) { 16706 aRequest->MayRejectPromise("No active window"); 16707 return; 16708 } 16709 16710 if (aApplyFullscreenDirectly || 16711 ShouldApplyFullscreenDirectly(this, rootWin)) { 16712 ApplyFullscreen(std::move(aRequest)); 16713 return; 16714 } 16715 16716 if (!CheckFullscreenAllowedElementType(aRequest->Element())) { 16717 aRequest->Reject("FullscreenDeniedNotHTMLSVGOrMathML"); 16718 return; 16719 } 16720 16721 // See if we're waiting on an exit. If so, just make this one pending. 16722 PendingFullscreenChangeList::Iterator<FullscreenExit> iter( 16723 this, PendingFullscreenChangeList::eDocumentsWithSameRoot); 16724 if (!iter.AtEnd()) { 16725 PendingFullscreenChangeList::Add(std::move(aRequest)); 16726 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true); 16727 return; 16728 } 16729 16730 // We don't need to check element ready before this point, because 16731 // if we called ApplyFullscreen, it would check that for us. 16732 if (!FullscreenElementReadyCheck(*aRequest)) { 16733 return; 16734 } 16735 16736 PendingFullscreenChangeList::Add(std::move(aRequest)); 16737 // Make the window fullscreen. 16738 rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true); 16739 } 16740 16741 /* static */ 16742 bool Document::HandlePendingFullscreenRequests(Document* aDoc) { 16743 bool handled = false; 16744 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter( 16745 aDoc, PendingFullscreenChangeList::eDocumentsWithSameRoot); 16746 while (!iter.AtEnd()) { 16747 UniquePtr<FullscreenRequest> request = iter.TakeAndNext(); 16748 Document* doc = request->Document(); 16749 if (doc->ApplyFullscreen(std::move(request))) { 16750 handled = true; 16751 } 16752 } 16753 return handled; 16754 } 16755 16756 /* static */ 16757 void Document::ClearPendingFullscreenRequests(Document* aDoc) { 16758 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter( 16759 aDoc, PendingFullscreenChangeList::eInclusiveDescendants); 16760 while (!iter.AtEnd()) { 16761 UniquePtr<FullscreenRequest> request = iter.TakeAndNext(); 16762 request->MayRejectPromise("Fullscreen request aborted"); 16763 } 16764 } 16765 16766 void Document::AddPendingFullscreenEvent( 16767 UniquePtr<PendingFullscreenEvent> aPendingEvent) { 16768 const bool wasEmpty = mPendingFullscreenEvents.IsEmpty(); 16769 mPendingFullscreenEvents.AppendElement(std::move(aPendingEvent)); 16770 if (wasEmpty) { 16771 MaybeScheduleRenderingPhases({RenderingPhase::FullscreenSteps}); 16772 } 16773 } 16774 16775 // https://fullscreen.spec.whatwg.org/#run-the-fullscreen-steps 16776 void Document::RunFullscreenSteps() { 16777 auto events = std::move(mPendingFullscreenEvents); 16778 for (auto& event : events) { 16779 event->Dispatch(this); 16780 } 16781 } 16782 16783 bool Document::HasPendingFullscreenRequests() { 16784 PendingFullscreenChangeList::Iterator<FullscreenRequest> iter( 16785 this, PendingFullscreenChangeList::eDocumentsWithSameRoot); 16786 return !iter.AtEnd(); 16787 } 16788 16789 MOZ_CAN_RUN_SCRIPT_BOUNDARY 16790 bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) { 16791 if (!FullscreenElementReadyCheck(*aRequest)) { 16792 return false; 16793 } 16794 16795 Element* elem = aRequest->Element(); 16796 16797 RefPtr<nsINode> hideUntil = elem->GetTopmostPopoverAncestor( 16798 PopoverAttributeState::Auto, nullptr, false); 16799 if (!hideUntil) { 16800 hideUntil = OwnerDoc(); 16801 } 16802 16803 RefPtr<Document> doc = aRequest->Document(); 16804 doc->HideAllPopoversUntil(*hideUntil, false, true); 16805 16806 // Stash a reference to any existing fullscreen doc, we'll use this later 16807 // to detect if the origin which is fullscreen has changed. 16808 nsCOMPtr<Document> previousFullscreenDoc = GetFullscreenLeaf(this); 16809 16810 // Stores a list of documents which we must dispatch "fullscreenchange" 16811 // too. We're required by the spec to dispatch the events in root-to-leaf 16812 // order, but we traverse the doctree in a leaf-to-root order, so we save 16813 // references to the documents we must dispatch to so that we get the order 16814 // as specified. 16815 AutoTArray<Document*, 8> changed; 16816 16817 // Remember the root document, so that if a fullscreen document is hidden 16818 // we can reset fullscreen state in the remaining visible fullscreen 16819 // documents. 16820 Document* fullScreenRootDoc = 16821 nsContentUtils::GetInProcessSubtreeRootDocument(this); 16822 16823 // If a document is already in fullscreen, then unlock the mouse pointer 16824 // before setting a new document to fullscreen 16825 PointerLockManager::Unlock("Document::ApplyFullscreen"); 16826 16827 // Set the fullscreen element. This sets the fullscreen style on the 16828 // element, and the fullscreen-ancestor styles on ancestors of the element 16829 // in this document. 16830 SetFullscreenElement(*elem); 16831 // Set the iframe fullscreen flag. 16832 if (auto* iframe = HTMLIFrameElement::FromNode(elem)) { 16833 iframe->SetFullscreenFlag(true); 16834 } 16835 changed.AppendElement(this); 16836 16837 // Propagate up the document hierarchy, setting the fullscreen element as 16838 // the element's container in ancestor documents. This also sets the 16839 // appropriate css styles as well. Note we don't propagate down the 16840 // document hierarchy, the fullscreen element (or its container) is not 16841 // visible there. Stop when we reach the root document. 16842 Document* child = this; 16843 while (true) { 16844 child->SetFullscreenRoot(fullScreenRootDoc); 16845 16846 // When entering fullscreen, reset the RCD's resolution to the intrinsic 16847 // resolution, otherwise the fullscreen content could be sized larger than 16848 // the screen (since fullscreen is implemented using position:fixed and 16849 // fixed elements are sized to the layout viewport). 16850 // This also ensures that things like video controls aren't zoomed in 16851 // when in fullscreen mode. 16852 if (PresShell* presShell = child->GetPresShell()) { 16853 if (RefPtr<MobileViewportManager> manager = 16854 presShell->GetMobileViewportManager()) { 16855 // Save the previous resolution so it can be restored. 16856 child->mSavedResolution = presShell->GetResolution(); 16857 presShell->SetResolutionAndScaleTo( 16858 manager->ComputeIntrinsicResolution(), 16859 ResolutionChangeOrigin::MainThreadRestore); 16860 } 16861 } 16862 16863 NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc, 16864 "Fullscreen root should be set!"); 16865 if (child == fullScreenRootDoc) { 16866 break; 16867 } 16868 16869 Element* element = child->GetEmbedderElement(); 16870 if (!element) { 16871 // We've reached the root.No more changes need to be made 16872 // to the top layer stacks of documents further up the tree. 16873 break; 16874 } 16875 16876 Document* parent = child->GetInProcessParentDocument(); 16877 16878 // If this is true, nothing above this node will have changed, stopping here 16879 // prevents us from sending duplicate events. 16880 if (parent->GetUnretargetedFullscreenElement() == element) { 16881 break; 16882 } 16883 parent->SetFullscreenElement(*element); 16884 changed.AppendElement(parent); 16885 child = parent; 16886 } 16887 16888 FullscreenRoots::Add(this); 16889 16890 // If it is the first entry of the fullscreen, trigger an event so 16891 // that the UI can response to this change, e.g. hide chrome, or 16892 // notifying parent process to enter fullscreen. Note that chrome 16893 // code may also want to listen to MozDOMFullscreen:NewOrigin event 16894 // to pop up warning UI. 16895 // We also need to propagate the message in the JSWA message chain, 16896 // so that if out of process sub-frames, that has requested fs 16897 // gets notified to resume it's request. We can know, that the request did not 16898 // originate from this process, if the JS promise is null. 16899 if (!aRequest->GetPromise() || !previousFullscreenDoc) { 16900 MOZ_ASSERT( 16901 (previousFullscreenDoc && 16902 ElementIsRemoteFrame(child->GetUnretargetedFullscreenElement())) || 16903 !previousFullscreenDoc); 16904 PropagateFullscreenRequest(this, elem); 16905 } 16906 16907 // The origin which is fullscreen gets changed. Trigger an event so 16908 // that the chrome knows to pop up a warning UI. Note that 16909 // previousFullscreenDoc == nullptr upon first entry, we show the warning UI 16910 // directly as soon as chrome document goes into fullscreen state. Also note 16911 // that, in a multi-process browser, the code in content process is 16912 // responsible for sending message with the origin to its parent, and the 16913 // parent shouldn't rely on this event itself. 16914 if (aRequest->mShouldNotifyNewOrigin && previousFullscreenDoc && 16915 !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) { 16916 DispatchFullscreenNewOriginEvent(this); 16917 } 16918 16919 // Dispatch "fullscreenchange" events. Note that the loop order is 16920 // reversed so that events are dispatched in the tree order as 16921 // indicated in the spec. 16922 for (Document* d : Reversed(changed)) { 16923 DispatchFullscreenChange(*d, d->GetUnretargetedFullscreenElement()); 16924 } 16925 aRequest->MayResolvePromise(); 16926 return true; 16927 } 16928 16929 void Document::ClearOrientationPendingPromise() { 16930 mOrientationPendingPromise = nullptr; 16931 } 16932 16933 bool Document::SetOrientationPendingPromise(Promise* aPromise) { 16934 if (mIsGoingAway) { 16935 return false; 16936 } 16937 16938 mOrientationPendingPromise = aPromise; 16939 return true; 16940 } 16941 16942 void Document::MaybeSkipTransitionAfterVisibilityChange() { 16943 if (Hidden() && mActiveViewTransition) { 16944 mActiveViewTransition->SkipTransition(SkipTransitionReason::DocumentHidden); 16945 } 16946 } 16947 16948 // https://drafts.csswg.org/css-view-transitions-1/#schedule-the-update-callback 16949 void Document::ScheduleViewTransitionUpdateCallback(ViewTransition* aVt) { 16950 MOZ_ASSERT(aVt); 16951 const bool hasTasks = !mViewTransitionUpdateCallbacks.IsEmpty(); 16952 16953 // 1. Append transition to transition’s relevant settings object’s update 16954 // callback queue. 16955 mViewTransitionUpdateCallbacks.AppendElement(aVt); 16956 16957 // 2. Queue a global task on the DOM manipulation task source, given 16958 // transition’s relevant global object, to flush the update callback queue. 16959 // Note: |hasTasks| means the list was not empty, so there must be a global 16960 // task there already. 16961 if (!hasTasks) { 16962 Dispatch(NewRunnableMethod( 16963 "Document::FlushViewTransitionUpdateCallbackQueue", this, 16964 &Document::FlushViewTransitionUpdateCallbackQueue)); 16965 } 16966 } 16967 16968 // https://drafts.csswg.org/css-view-transitions-1/#flush-the-update-callback-queue 16969 void Document::FlushViewTransitionUpdateCallbackQueue() { 16970 // 1. For each transition in document’s update callback queue, call the update 16971 // callback given transition. 16972 // Note: we move mViewTransitionUpdateCallbacks into a temporary array to make 16973 // sure no one updates the array when iterating. 16974 auto callbacks = std::move(mViewTransitionUpdateCallbacks); 16975 MOZ_ASSERT(mViewTransitionUpdateCallbacks.IsEmpty()); 16976 for (RefPtr<ViewTransition>& vt : callbacks) { 16977 MOZ_KnownLive(vt)->CallUpdateCallback(IgnoreErrors()); 16978 } 16979 16980 // 2. Set document’s update callback queue to an empty list. 16981 // mViewTransitionUpdateCallbacks is empty after the 1st step. 16982 } 16983 16984 // https://html.spec.whatwg.org/#update-the-visibility-state 16985 void Document::UpdateVisibilityState(DispatchVisibilityChange aDispatchEvent) { 16986 const dom::VisibilityState visibilityState = ComputeVisibilityState(); 16987 if (mVisibilityState == visibilityState) { 16988 // 1. If document's visibility state equals visibilityState, then return. 16989 return; 16990 } 16991 // 2. Set document's visibility state to visibilityState. 16992 mVisibilityState = visibilityState; 16993 if (aDispatchEvent == DispatchVisibilityChange::Yes) { 16994 // 3. Fire an event named visibilitychange at document, with its bubbles 16995 // attribute initialized to true. 16996 nsContentUtils::DispatchTrustedEvent(this, this, u"visibilitychange"_ns, 16997 CanBubble::eYes, Cancelable::eNo); 16998 } 16999 // TODO 4. Run the screen orientation change steps with document. 17000 17001 // 5. Run the view transition page visibility change steps with document. 17002 // https://drafts.csswg.org/css-view-transitions/#view-transition-page-visibility-change-steps 17003 const bool visible = !Hidden(); 17004 if (mActiveViewTransition && !visible) { 17005 // The mActiveViewTransition check is an optimization: We don't allow 17006 // creating transitions while the doc is hidden. 17007 Dispatch( 17008 NewRunnableMethod("MaybeSkipTransitionAfterVisibilityChange", this, 17009 &Document::MaybeSkipTransitionAfterVisibilityChange)); 17010 } 17011 17012 NotifyActivityChanged(); 17013 if (visible) { 17014 MaybeScheduleRendering(); 17015 MaybeActiveMediaComponents(); 17016 } 17017 17018 for (auto* listener : mWorkerListeners) { 17019 listener->OnVisible(visible); 17020 } 17021 17022 // https://w3c.github.io/screen-wake-lock/#handling-document-loss-of-visibility 17023 if (!visible) { 17024 UnlockAllWakeLocks(WakeLockType::Screen); 17025 } 17026 } 17027 17028 void Document::AddWorkerDocumentListener(WorkerDocumentListener* aListener) { 17029 mWorkerListeners.Insert(aListener); 17030 aListener->OnVisible(!Hidden()); 17031 } 17032 17033 void Document::RemoveWorkerDocumentListener(WorkerDocumentListener* aListener) { 17034 mWorkerListeners.Remove(aListener); 17035 } 17036 17037 VisibilityState Document::ComputeVisibilityState() const { 17038 // We have to check a few pieces of information here: 17039 // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters. 17040 // 2) Do we have an outer window? If not, we're hidden. Note that we don't 17041 // want to use GetWindow here because it does weird groveling for windows 17042 // in some cases. 17043 // 3) Is our outer window background? If so, we're hidden. 17044 // Otherwise, we're visible. 17045 if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() || 17046 mWindow->GetOuterWindow()->IsBackground()) { 17047 return dom::VisibilityState::Hidden; 17048 } 17049 17050 return dom::VisibilityState::Visible; 17051 } 17052 17053 void Document::PostVisibilityUpdateEvent() { 17054 nsCOMPtr<nsIRunnable> event = NewRunnableMethod<DispatchVisibilityChange>( 17055 "Document::UpdateVisibilityState", this, &Document::UpdateVisibilityState, 17056 DispatchVisibilityChange::Yes); 17057 Dispatch(event.forget()); 17058 } 17059 17060 void Document::MaybeActiveMediaComponents() { 17061 auto* window = GetWindow(); 17062 if (!window || !window->ShouldDelayMediaFromStart()) { 17063 return; 17064 } 17065 window->ActivateMediaComponents(); 17066 } 17067 17068 void Document::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const { 17069 nsINode::AddSizeOfExcludingThis(aWindowSizes, 17070 &aWindowSizes.mDOMSizes.mDOMOtherSize); 17071 17072 for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextSibling()) { 17073 AddSizeOfNodeTree(*kid, aWindowSizes); 17074 } 17075 17076 // IMPORTANT: for our ComputedValues measurements, we want to measure 17077 // ComputedValues accessible from DOM elements before ComputedValues not 17078 // accessible from DOM elements (i.e. accessible only from the frame tree). 17079 // 17080 // Therefore, the measurement of the Document superclass must happen after 17081 // the measurement of DOM nodes (above), because Document contains the 17082 // PresShell, which contains the frame tree. 17083 if (mPresShell) { 17084 mPresShell->AddSizeOfIncludingThis(aWindowSizes); 17085 } 17086 17087 if (mStyleSet) { 17088 mStyleSet->AddSizeOfIncludingThis(aWindowSizes); 17089 } 17090 17091 aWindowSizes.mPropertyTablesSize += 17092 mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf); 17093 17094 if (EventListenerManager* elm = GetExistingListenerManager()) { 17095 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount(); 17096 } 17097 17098 if (mNodeInfoManager) { 17099 mNodeInfoManager->AddSizeOfIncludingThis(aWindowSizes); 17100 } 17101 17102 aWindowSizes.mDOMSizes.mDOMMediaQueryLists += 17103 mDOMMediaQueryLists.sizeOfExcludingThis( 17104 aWindowSizes.mState.mMallocSizeOf); 17105 17106 for (const MediaQueryList* mql : mDOMMediaQueryLists) { 17107 aWindowSizes.mDOMSizes.mDOMMediaQueryLists += 17108 mql->SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf); 17109 } 17110 17111 DocumentOrShadowRoot::AddSizeOfExcludingThis(aWindowSizes); 17112 17113 for (auto& sheetArray : mAdditionalSheets) { 17114 AddSizeOfOwnedSheetArrayExcludingThis(aWindowSizes, sheetArray); 17115 } 17116 // Lumping in the loader with the style-sheets size is not ideal, 17117 // but most of the things in there are in fact stylesheets, so it 17118 // doesn't seem worthwhile to separate it out. 17119 // This can be null if we've already been unlinked. 17120 if (mCSSLoader) { 17121 aWindowSizes.mLayoutStyleSheetsSize += 17122 mCSSLoader->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf); 17123 } 17124 17125 aWindowSizes.mDOMSizes.mDOMResizeObserverControllerSize += 17126 mResizeObservers.ShallowSizeOfExcludingThis( 17127 aWindowSizes.mState.mMallocSizeOf); 17128 17129 if (mAttributeStyles) { 17130 aWindowSizes.mDOMSizes.mDOMOtherSize += 17131 mAttributeStyles->DOMSizeOfIncludingThis( 17132 aWindowSizes.mState.mMallocSizeOf); 17133 } 17134 17135 if (mRadioGroupContainer) { 17136 aWindowSizes.mDOMSizes.mDOMOtherSize += 17137 mRadioGroupContainer->SizeOfIncludingThis( 17138 aWindowSizes.mState.mMallocSizeOf); 17139 } 17140 17141 aWindowSizes.mDOMSizes.mDOMOtherSize += 17142 mStyledLinks.ShallowSizeOfExcludingThis( 17143 aWindowSizes.mState.mMallocSizeOf); 17144 17145 // Measurement of the following members may be added later if DMD finds it 17146 // is worthwhile: 17147 // - mMidasCommandManager 17148 // - many! 17149 } 17150 17151 void Document::DocAddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const { 17152 aWindowSizes.mDOMSizes.mDOMOtherSize += 17153 aWindowSizes.mState.mMallocSizeOf(this); 17154 DocAddSizeOfExcludingThis(aWindowSizes); 17155 } 17156 17157 void Document::AddSizeOfExcludingThis(nsWindowSizes& aSizes, 17158 size_t* aNodeSize) const { 17159 // This AddSizeOfExcludingThis() overrides the one from nsINode. But 17160 // nsDocuments can only appear at the top of the DOM tree, and we use the 17161 // specialized DocAddSizeOfExcludingThis() in that case. So this should never 17162 // be called. 17163 MOZ_CRASH(); 17164 } 17165 17166 /* static */ 17167 void Document::AddSizeOfNodeTree(nsINode& aNode, nsWindowSizes& aWindowSizes) { 17168 size_t nodeSize = 0; 17169 aNode.AddSizeOfIncludingThis(aWindowSizes, &nodeSize); 17170 17171 // This is where we transfer the nodeSize obtained from 17172 // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes. 17173 switch (aNode.NodeType()) { 17174 case nsINode::ELEMENT_NODE: 17175 aWindowSizes.mDOMSizes.mDOMElementNodesSize += nodeSize; 17176 break; 17177 case nsINode::TEXT_NODE: 17178 aWindowSizes.mDOMSizes.mDOMTextNodesSize += nodeSize; 17179 break; 17180 case nsINode::CDATA_SECTION_NODE: 17181 aWindowSizes.mDOMSizes.mDOMCDATANodesSize += nodeSize; 17182 break; 17183 case nsINode::COMMENT_NODE: 17184 aWindowSizes.mDOMSizes.mDOMCommentNodesSize += nodeSize; 17185 break; 17186 default: 17187 aWindowSizes.mDOMSizes.mDOMOtherSize += nodeSize; 17188 break; 17189 } 17190 17191 if (EventListenerManager* elm = aNode.GetExistingListenerManager()) { 17192 aWindowSizes.mDOMEventListenersCount += elm->ListenerCount(); 17193 } 17194 17195 if (aNode.IsContent()) { 17196 nsTArray<nsIContent*> anonKids; 17197 nsContentUtils::AppendNativeAnonymousChildren(aNode.AsContent(), anonKids, 17198 nsIContent::eAllChildren); 17199 for (nsIContent* anonKid : anonKids) { 17200 AddSizeOfNodeTree(*anonKid, aWindowSizes); 17201 } 17202 17203 if (auto* element = Element::FromNode(aNode)) { 17204 if (ShadowRoot* shadow = element->GetShadowRoot()) { 17205 AddSizeOfNodeTree(*shadow, aWindowSizes); 17206 } 17207 } 17208 } 17209 17210 // NOTE(emilio): If you feel smart and want to change this function to use 17211 // GetNextNode(), think twice, since you'd need to handle <xbl:content> in a 17212 // sane way, and kids of <content> won't point to the parent, so we'd never 17213 // find the root node where we should stop at. 17214 for (nsIContent* kid = aNode.GetFirstChild(); kid; 17215 kid = kid->GetNextSibling()) { 17216 AddSizeOfNodeTree(*kid, aWindowSizes); 17217 } 17218 } 17219 17220 already_AddRefed<Document> Document::Constructor(const GlobalObject& aGlobal, 17221 ErrorResult& rv) { 17222 nsCOMPtr<nsIScriptGlobalObject> global = 17223 do_QueryInterface(aGlobal.GetAsSupports()); 17224 if (!global) { 17225 rv.Throw(NS_ERROR_UNEXPECTED); 17226 return nullptr; 17227 } 17228 17229 nsCOMPtr<nsIScriptObjectPrincipal> prin = 17230 do_QueryInterface(aGlobal.GetAsSupports()); 17231 if (!prin) { 17232 rv.Throw(NS_ERROR_UNEXPECTED); 17233 return nullptr; 17234 } 17235 17236 nsCOMPtr<nsIURI> uri; 17237 NS_NewURI(getter_AddRefs(uri), "about:blank"); 17238 if (!uri) { 17239 rv.Throw(NS_ERROR_OUT_OF_MEMORY); 17240 return nullptr; 17241 } 17242 17243 nsCOMPtr<Document> doc; 17244 nsresult res = 17245 NS_NewDOMDocument(getter_AddRefs(doc), VoidString(), u""_ns, nullptr, uri, 17246 uri, prin->GetPrincipal(), LoadedAsData::AsData, global, 17247 DocumentFlavor::Plain); 17248 if (NS_FAILED(res)) { 17249 rv.Throw(res); 17250 return nullptr; 17251 } 17252 17253 doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE); 17254 17255 return doc.forget(); 17256 } 17257 17258 UniquePtr<XPathExpression> Document::CreateExpression( 17259 const nsAString& aExpression, XPathNSResolver* aResolver, ErrorResult& rv) { 17260 return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv); 17261 } 17262 17263 nsINode* Document::CreateNSResolver(nsINode& aNodeResolver) { 17264 return XPathEvaluator()->CreateNSResolver(aNodeResolver); 17265 } 17266 17267 already_AddRefed<XPathResult> Document::Evaluate( 17268 JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode, 17269 XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult, 17270 ErrorResult& rv) { 17271 return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver, 17272 aType, aResult, rv); 17273 } 17274 17275 already_AddRefed<nsIAppWindow> Document::GetAppWindowIfToplevelChrome() const { 17276 nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell(); 17277 if (!item) { 17278 return nullptr; 17279 } 17280 nsCOMPtr<nsIDocShellTreeOwner> owner; 17281 item->GetTreeOwner(getter_AddRefs(owner)); 17282 nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(owner); 17283 if (!appWin) { 17284 return nullptr; 17285 } 17286 nsCOMPtr<nsIDocShell> appWinShell; 17287 appWin->GetDocShell(getter_AddRefs(appWinShell)); 17288 if (!SameCOMIdentity(appWinShell, item)) { 17289 return nullptr; 17290 } 17291 return appWin.forget(); 17292 } 17293 17294 WindowContext* Document::GetTopLevelWindowContext() const { 17295 WindowContext* windowContext = GetWindowContext(); 17296 return windowContext ? windowContext->TopWindowContext() : nullptr; 17297 } 17298 17299 Document* Document::GetTopLevelContentDocumentIfSameProcess() { 17300 Document* parent; 17301 17302 if (!mLoadedAsData) { 17303 parent = this; 17304 } else { 17305 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject()); 17306 if (!window) { 17307 return nullptr; 17308 } 17309 17310 parent = window->GetExtantDoc(); 17311 if (!parent) { 17312 return nullptr; 17313 } 17314 } 17315 17316 do { 17317 if (parent->IsTopLevelContentDocument()) { 17318 break; 17319 } 17320 17321 // If we ever have a non-content parent before we hit a toplevel content 17322 // parent, then we're never going to find one. Just bail. 17323 if (!parent->IsContentDocument()) { 17324 return nullptr; 17325 } 17326 17327 parent = parent->GetInProcessParentDocument(); 17328 } while (parent); 17329 17330 return parent; 17331 } 17332 17333 const Document* Document::GetTopLevelContentDocumentIfSameProcess() const { 17334 return const_cast<Document*>(this)->GetTopLevelContentDocumentIfSameProcess(); 17335 } 17336 17337 void Document::PropagateImageUseCounters(Document* aReferencingDocument) { 17338 MOZ_ASSERT(IsBeingUsedAsImage()); 17339 MOZ_ASSERT(aReferencingDocument); 17340 17341 if (!aReferencingDocument->mShouldReportUseCounters) { 17342 // No need to propagate use counters to a document that itself won't report 17343 // use counters. 17344 return; 17345 } 17346 17347 MOZ_LOG(gUseCountersLog, LogLevel::Debug, 17348 ("PropagateImageUseCounters from %s to %s", 17349 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get(), 17350 nsContentUtils::TruncatedURLForDisplay( 17351 aReferencingDocument->mDocumentURI) 17352 .get())); 17353 17354 if (aReferencingDocument->IsBeingUsedAsImage()) { 17355 NS_WARNING( 17356 "Page use counters from nested image documents may not " 17357 "propagate to the top-level document (bug 1657805)"); 17358 } 17359 17360 SetCssUseCounterBits(); 17361 aReferencingDocument->mChildDocumentUseCounters |= mUseCounters; 17362 aReferencingDocument->mChildDocumentUseCounters |= mChildDocumentUseCounters; 17363 } 17364 17365 bool Document::HasScriptsBlockedBySandbox() const { 17366 return mSandboxFlags & SANDBOXED_SCRIPTS; 17367 } 17368 17369 void Document::SetCssUseCounterBits() { 17370 if (StaticPrefs::layout_css_use_counters_enabled()) { 17371 for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; ++i) { 17372 auto id = NonCustomCSSPropertyId(i); 17373 if (Servo_IsPropertyIdRecordedInUseCounter(mStyleUseCounters.get(), id)) { 17374 SetUseCounter(nsCSSProps::UseCounterFor(id)); 17375 } 17376 } 17377 } 17378 17379 if (StaticPrefs::layout_css_use_counters_unimplemented_enabled()) { 17380 for (size_t i = 0; i < size_t(CountedUnknownProperty::Count); ++i) { 17381 auto id = CountedUnknownProperty(i); 17382 if (Servo_IsUnknownPropertyRecordedInUseCounter(mStyleUseCounters.get(), 17383 id)) { 17384 SetUseCounter(UseCounter(eUseCounter_FirstCountedUnknownProperty + i)); 17385 } 17386 } 17387 } 17388 } 17389 17390 void Document::InitUseCounters() { 17391 // We can be called more than once, e.g. when session history navigation shows 17392 // us a second time. 17393 if (mUseCountersInitialized) { 17394 return; 17395 } 17396 mUseCountersInitialized = true; 17397 17398 if (!ShouldIncludeInTelemetry()) { 17399 return; 17400 } 17401 17402 // Now we know for sure that we should report use counters from this document. 17403 mShouldReportUseCounters = true; 17404 17405 WindowContext* top = GetWindowContextForPageUseCounters(); 17406 if (!top) { 17407 // This is the case for SVG image documents. They are not displayed in a 17408 // window, but we still do want to record document use counters for them. 17409 // 17410 // Page use counter propagation is handled in PropagateImageUseCounters, 17411 // so there is no need to use the cross-process machinery to send them. 17412 MOZ_LOG(gUseCountersLog, LogLevel::Debug, 17413 ("InitUseCounters for a non-displayed document [%s]", 17414 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get())); 17415 return; 17416 } 17417 17418 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild(); 17419 if (!wgc) { 17420 return; 17421 } 17422 17423 MOZ_LOG(gUseCountersLog, LogLevel::Debug, 17424 ("InitUseCounters for a displayed document: %" PRIu64 " -> %" PRIu64 17425 " [from %s]", 17426 wgc->InnerWindowId(), top->Id(), 17427 nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get())); 17428 17429 // Inform the parent process that we will send it page use counters later on. 17430 wgc->SendExpectPageUseCounters(top); 17431 mShouldSendPageUseCounters = true; 17432 } 17433 17434 // We keep separate counts for individual documents and top-level 17435 // pages to more accurately track how many web pages might break if 17436 // certain features were removed. Consider the case of a single 17437 // HTML document with several SVG images and/or iframes with 17438 // sub-documents of their own. If we maintained a single set of use 17439 // counters and all the sub-documents use a particular feature, then 17440 // telemetry would indicate that we would be breaking N documents if 17441 // that feature were removed. Whereas with a document/top-level 17442 // page split, we can see that N documents would be affected, but 17443 // only a single web page would be affected. 17444 // 17445 // The difference between the values of these two counts and the 17446 // related use counters below tell us how many pages did *not* use 17447 // the feature in question. For instance, if we see that a given 17448 // session has destroyed 30 content documents, but a particular use 17449 // counter shows only a count of 5, we can infer that the use 17450 // counter was *not* used in 25 of those 30 documents. 17451 // 17452 // We do things this way, rather than accumulating a boolean flag 17453 // for each use counter, to avoid sending data for features 17454 // that don't get widely used. Doing things in this fashion means 17455 // smaller telemetry payloads and faster processing on the server 17456 // side. 17457 void Document::ReportDocumentUseCounters() { 17458 if (!mShouldReportUseCounters || mReportedDocumentUseCounters) { 17459 return; 17460 } 17461 17462 mReportedDocumentUseCounters = true; 17463 17464 // Note that a document is being destroyed. See the comment above for how 17465 // use counter data are interpreted relative to this measurement. 17466 glean::use_counter::content_documents_destroyed.Add(); 17467 17468 // Ask all of our resource documents to report their own document use 17469 // counters. 17470 EnumerateExternalResources([](Document& aDoc) { 17471 aDoc.ReportDocumentUseCounters(); 17472 return CallState::Continue; 17473 }); 17474 17475 // Copy StyleUseCounters into our document use counters. 17476 SetCssUseCounterBits(); 17477 17478 Maybe<nsCString> urlForLogging; 17479 const bool dumpCounters = StaticPrefs::dom_use_counters_dump_document(); 17480 if (dumpCounters) { 17481 urlForLogging.emplace( 17482 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI())); 17483 } 17484 17485 // Report our per-document use counters. 17486 for (int32_t c = 0; c < eUseCounter_Count; ++c) { 17487 auto uc = static_cast<UseCounter>(c); 17488 if (!mUseCounters[uc]) { 17489 continue; 17490 } 17491 17492 const char* metricName = IncrementUseCounter(uc, /* aIsPage = */ false); 17493 if (dumpCounters) { 17494 printf_stderr("USE_COUNTER_DOCUMENT: %s - %s\n", metricName, 17495 urlForLogging->get()); 17496 } 17497 } 17498 } 17499 17500 void Document::ReportShadowedProperties() { 17501 if (!ShouldIncludeInTelemetry()) { 17502 return; 17503 } 17504 17505 for (const nsString& property : mShadowedHTMLDocumentProperties) { 17506 glean::security::ShadowedHtmlDocumentPropertyAccessExtra extra = {}; 17507 extra.name = Some(NS_ConvertUTF16toUTF8(property)); 17508 glean::security::shadowed_html_document_property_access.Record(Some(extra)); 17509 } 17510 } 17511 17512 void Document::ReportLCP() { 17513 // Do not record LCP in any histogram if the same value is being recorded 17514 // in the domain pageload event. 17515 if (mPageloadEventData.HasDomain()) { 17516 return; 17517 } 17518 17519 const nsDOMNavigationTiming* timing = GetNavigationTiming(); 17520 17521 if (!ShouldIncludeInTelemetry() || !IsTopLevelContentDocument() || !timing || 17522 !timing->DocShellHasBeenActiveSinceNavigationStart()) { 17523 return; 17524 } 17525 17526 TimeStamp lcpTime = timing->GetLargestContentfulRenderTimeStamp(); 17527 17528 if (!lcpTime) { 17529 return; 17530 } 17531 17532 mozilla::glean::perf::largest_contentful_paint.AccumulateRawDuration( 17533 lcpTime - timing->GetNavigationStartTimeStamp()); 17534 17535 if (!GetChannel()) { 17536 return; 17537 } 17538 17539 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(GetChannel())); 17540 if (!timedChannel) { 17541 return; 17542 } 17543 17544 TimeStamp responseStart; 17545 timedChannel->GetResponseStart(&responseStart); 17546 17547 if (!responseStart) { 17548 // This happens when getting a response from the cache. 17549 mozilla::glean::perf::largest_contentful_paint_from_response_start 17550 .AccumulateRawDuration(lcpTime - timing->GetNavigationStartTimeStamp()); 17551 } else { 17552 mozilla::glean::perf::largest_contentful_paint_from_response_start 17553 .AccumulateRawDuration(lcpTime - responseStart); 17554 } 17555 17556 if (profiler_thread_is_being_profiled_for_markers()) { 17557 MarkerInnerWindowId innerWindowID = 17558 MarkerInnerWindowIdFromDocShell(GetDocShell()); 17559 GetNavigationTiming()->MaybeAddLCPProfilerMarker(innerWindowID); 17560 } 17561 } 17562 17563 void Document::SendPageUseCounters() { 17564 if (!mShouldReportUseCounters || !mShouldSendPageUseCounters) { 17565 return; 17566 } 17567 17568 // Ask all of our resource documents to send their own document use 17569 // counters to the parent process to be counted as page use counters. 17570 EnumerateExternalResources([](Document& aDoc) { 17571 aDoc.SendPageUseCounters(); 17572 return CallState::Continue; 17573 }); 17574 17575 // Send our use counters to the parent process to accumulate them towards the 17576 // page use counters for the top-level document. 17577 // 17578 // We take our own document use counters (those in mUseCounters) and any child 17579 // document use counters (those in mChildDocumentUseCounters) that have been 17580 // explicitly propagated up to us, which includes resource documents, static 17581 // clones, and SVG images. 17582 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild(); 17583 if (!wgc) { 17584 MOZ_ASSERT_UNREACHABLE( 17585 "SendPageUseCounters should be called while we still have access " 17586 "to our WindowContext"); 17587 MOZ_LOG(gUseCountersLog, LogLevel::Debug, 17588 (" > too late to send page use counters")); 17589 return; 17590 } 17591 17592 MOZ_LOG(gUseCountersLog, LogLevel::Debug, 17593 ("Sending page use counters: from WindowContext %" PRIu64 " [%s]", 17594 wgc->WindowContext()->Id(), 17595 nsContentUtils::TruncatedURLForDisplay(GetDocumentURI()).get())); 17596 17597 // Copy StyleUseCounters into our document use counters. 17598 SetCssUseCounterBits(); 17599 17600 UseCounters counters = mUseCounters | mChildDocumentUseCounters; 17601 wgc->SendAccumulatePageUseCounters(counters); 17602 } 17603 17604 void Document::MaybeRecomputePartitionKey() { 17605 // We only need to recompute the partition key for the top-level content 17606 // document. 17607 if (!IsTopLevelContentDocument()) { 17608 return; 17609 } 17610 17611 // Bail out early if there is no cookieJarSettings for this document. For 17612 // example, a chrome document. 17613 if (!mCookieJarSettings) { 17614 return; 17615 } 17616 17617 // Check whether the partition key matches the document's node principal. They 17618 // can be different if the document is sandboxed. In this case, the node 17619 // principal is a null principal. But the partitionKey of the 17620 // cookieJarSettings was derived from the channel URI of the top-level 17621 // loading, which isn't a null principal. Therefore, we need to recompute 17622 // the partition Key from the document's node principal to reflect the actual 17623 // partition Key. 17624 nsAutoCString originNoSuffix; 17625 nsresult rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix); 17626 NS_ENSURE_SUCCESS_VOID(rv); 17627 17628 nsCOMPtr<nsIURI> originURI; 17629 rv = NS_NewURI(getter_AddRefs(originURI), originNoSuffix); 17630 NS_ENSURE_SUCCESS_VOID(rv); 17631 17632 // Bail out early if we don't have a principal URI. 17633 if (!originURI) { 17634 return; 17635 } 17636 17637 OriginAttributes attrs; 17638 attrs.SetPartitionKey(originURI, false); 17639 17640 // We don't need to set the partition key if the cooieJarSettings' 17641 // partitionKey matches the document's node principal. 17642 if (attrs.mPartitionKey.Equals( 17643 net::CookieJarSettings::Cast(mCookieJarSettings) 17644 ->GetPartitionKey())) { 17645 return; 17646 } 17647 17648 // Set the partition key to the document's node principal. So we will use the 17649 // right partition key afterward. 17650 mozilla::net::CookieJarSettings::Cast(mCookieJarSettings) 17651 ->SetPartitionKey(originURI); 17652 } 17653 17654 bool Document::RecomputeResistFingerprinting(bool aForceRefreshRTPCallerType) { 17655 mOverriddenFingerprintingSettings.reset(); 17656 const bool previous = mShouldResistFingerprinting; 17657 17658 RefPtr<BrowsingContext> opener = 17659 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr; 17660 // If we have a parent or opener document, defer to it only when we have a 17661 // null principal (e.g. a sandboxed iframe or a data: uri) or when the 17662 // document's principal matches. This means we will defer about:blank, 17663 // about:srcdoc, blob and same-origin iframes/popups to the parent/opener, 17664 // but not cross-origin ones. Cross-origin iframes/popups may inherit a 17665 // CookieJarSettings.mShouldRFP = false bit however, which will be respected. 17666 auto shouldInheritFrom = [this](Document* aDoc) { 17667 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) || 17668 this->NodePrincipal()->GetIsNullPrincipal()); 17669 }; 17670 17671 if (shouldInheritFrom(mParentDocument)) { 17672 MOZ_LOG( 17673 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug, 17674 ("Inside RecomputeResistFingerprinting with URI %s and deferring " 17675 "to parent document %s", 17676 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null", 17677 mParentDocument->GetDocumentURI()->GetSpecOrDefault().get())); 17678 mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting( 17679 RFPTarget::IsAlwaysEnabledForPrecompute); 17680 mOverriddenFingerprintingSettings = 17681 mParentDocument->mOverriddenFingerprintingSettings; 17682 } else if (opener && shouldInheritFrom(opener->GetDocument())) { 17683 MOZ_LOG( 17684 nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug, 17685 ("Inside RecomputeResistFingerprinting with URI %s and deferring to " 17686 "opener document %s", 17687 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() : "null", 17688 opener->GetDocument()->GetDocumentURI()->GetSpecOrDefault().get())); 17689 mShouldResistFingerprinting = 17690 opener->GetDocument()->ShouldResistFingerprinting( 17691 RFPTarget::IsAlwaysEnabledForPrecompute); 17692 mOverriddenFingerprintingSettings = 17693 opener->GetDocument()->mOverriddenFingerprintingSettings; 17694 } else if (nsContentUtils::IsChromeDoc(this)) { 17695 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug, 17696 ("Inside RecomputeResistFingerprinting with a ChromeDoc")); 17697 17698 mShouldResistFingerprinting = false; 17699 mOverriddenFingerprintingSettings.reset(); 17700 } else if (mChannel) { 17701 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug, 17702 ("Inside RecomputeResistFingerprinting with URI %s", 17703 GetDocumentURI() ? GetDocumentURI()->GetSpecOrDefault().get() 17704 : "null")); 17705 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting( 17706 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute); 17707 17708 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 17709 mOverriddenFingerprintingSettings = 17710 loadInfo->GetOverriddenFingerprintingSettings(); 17711 } else { 17712 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug, 17713 ("Inside RecomputeResistFingerprinting fallback case.")); 17714 // We still preserve the behavior in the fallback case. But, it means there 17715 // might be some cases we haven't considered yet and we need to investigate 17716 // them. 17717 mShouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting( 17718 mChannel, RFPTarget::IsAlwaysEnabledForPrecompute); 17719 mOverriddenFingerprintingSettings.reset(); 17720 } 17721 17722 MOZ_LOG(nsContentUtils::ResistFingerprintingLog(), LogLevel::Debug, 17723 ("Finished RecomputeResistFingerprinting with result %x", 17724 mShouldResistFingerprinting)); 17725 17726 bool changed = previous != mShouldResistFingerprinting; 17727 if (changed || aForceRefreshRTPCallerType) { 17728 if (auto win = nsGlobalWindowInner::Cast(GetInnerWindow())) { 17729 win->RefreshReduceTimerPrecisionCallerType(); 17730 } 17731 } 17732 return changed; 17733 } 17734 17735 bool Document::ShouldResistFingerprinting(RFPTarget aTarget) const { 17736 return mShouldResistFingerprinting && 17737 nsRFPService::IsRFPEnabledFor(this->IsInPrivateBrowsing(), aTarget, 17738 mOverriddenFingerprintingSettings); 17739 } 17740 17741 void Document::RecordCanvasUsage(CanvasUsage& aUsage) { 17742 // Limit the number of recent canvas extraction uses that are tracked. 17743 // Because we are now covering more canvas extraction sources, we need to 17744 // increase this a bit 17745 const size_t kTrackedCanvasLimit = 15; 17746 // Timeout between different canvas extractions. 17747 const uint64_t kTimeoutUsec = 3000 * 1000; 17748 17749 uint64_t now = PR_Now(); 17750 17751 nsCString originNoSuffix; 17752 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) { 17753 MOZ_LOG(gFingerprinterDetection, LogLevel::Error, 17754 ("Document:: %p Could not get originsuffix", this)); 17755 return; 17756 } 17757 nsCOMPtr<nsIURI> uri = NodePrincipal()->GetURI(); 17758 if (!uri) { 17759 MOZ_LOG(gFingerprinterDetection, LogLevel::Error, 17760 ("Document:: %p Could not get uri", this)); 17761 return; 17762 } 17763 if (MOZ_LOG_TEST(gFingerprinterDetection, LogLevel::Debug)) { 17764 nsAutoCString filename; 17765 uint32_t lineNum = 0; 17766 filename.AssignLiteral("<unknown>"); 17767 JSContext* cx = nsContentUtils::GetCurrentJSContext(); 17768 if (cx) { 17769 JS::AutoFilename scriptFilename; 17770 JS::ColumnNumberOneOrigin colOneOrigin; 17771 if (JS::DescribeScriptedCaller(&scriptFilename, cx, &lineNum, 17772 &colOneOrigin)) { 17773 if (const char* file = scriptFilename.get()) { 17774 filename = nsDependentCString(file); 17775 } 17776 } 17777 } 17778 17779 nsAutoCString uriString; 17780 (void)uri->GetSpec(uriString); 17781 17782 MOZ_LOG(gFingerprinterDetection, LogLevel::Debug, 17783 ("Document:: %p %s recording canvas usage of type %s on %s in %s", 17784 this, originNoSuffix.get(), 17785 CanvasUsageSourceToString(aUsage.mUsageSource).get(), 17786 uriString.get(), filename.get())); 17787 } 17788 17789 // Check if we need to clear the usage data for this source. 17790 if (mCanvasUsageLastTimestamp != 0 && 17791 (now - mCanvasUsageLastTimestamp) > kTimeoutUsec) { 17792 MOZ_LOG( 17793 gFingerprinterDetection, LogLevel::Verbose, 17794 ("Document:: %p %s clearing canvas array", this, originNoSuffix.get())); 17795 mCanvasUsageData.Clear(); 17796 } else if (mCanvasUsageData.Length() > kTrackedCanvasLimit) { 17797 MOZ_LOG(gFingerprinterDetection, LogLevel::Verbose, 17798 ("Document:: %p %s removing oldest canvas " 17799 "usage in array", 17800 this, originNoSuffix.get())); 17801 mCanvasUsageData.RemoveElementAt(0); 17802 } else { 17803 MOZ_LOG(gFingerprinterDetection, LogLevel::Verbose, 17804 ("Document:: %p %s recorded canvas " 17805 "usage of type %s in array, records: %zu", 17806 this, originNoSuffix.get(), 17807 CanvasUsageSourceToString(aUsage.mUsageSource).get(), 17808 static_cast<size_t>(mCanvasUsageData.Length() + 1))); 17809 } 17810 17811 // Update the timestamp and append the new usage. 17812 mCanvasUsageLastTimestamp = now; 17813 mCanvasUsageData.AppendElement(aUsage); 17814 17815 nsIChannel* channel = GetChannel(); 17816 if (!channel) { 17817 MOZ_LOG( 17818 gFingerprinterDetection, LogLevel::Warning, 17819 ("Document:: %p %s no channel available", this, originNoSuffix.get())); 17820 17821 // Borrowed from ReComputeResistFingerprinting 17822 // which tells me this is probably a common problem... 17823 auto shouldInheritFrom = [this](Document* aDoc) { 17824 return aDoc && this->NodePrincipal() && 17825 (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) || 17826 this->NodePrincipal()->GetIsNullPrincipal()); 17827 }; 17828 17829 // Climb parent documents until we find a channel. 17830 Document* docToCheck = this; 17831 while (docToCheck && !channel) { 17832 if (docToCheck->mParentDocument && 17833 shouldInheritFrom(docToCheck->mParentDocument)) { 17834 channel = docToCheck->mParentDocument->GetChannel(); 17835 } 17836 docToCheck = docToCheck->mParentDocument; 17837 } 17838 17839 docToCheck = this; 17840 while (docToCheck && !channel) { 17841 RefPtr<BrowsingContext> opener = 17842 docToCheck->GetBrowsingContext() 17843 ? docToCheck->GetBrowsingContext()->GetOpener() 17844 : nullptr; 17845 docToCheck = opener ? opener->GetDocument() : nullptr; 17846 17847 if (docToCheck && shouldInheritFrom(docToCheck)) { 17848 channel = docToCheck->GetChannel(); 17849 } 17850 } 17851 17852 if (!channel) { 17853 MOZ_LOG(gFingerprinterDetection, LogLevel::Warning, 17854 ("Document:: %p %s still could not find a channel", this, 17855 originNoSuffix.get())); 17856 } 17857 } 17858 17859 nsRFPService::MaybeReportCanvasFingerprinter(mCanvasUsageData, channel, uri, 17860 originNoSuffix); 17861 } 17862 17863 void Document::RecordFontFingerprinting() { 17864 nsCString originNoSuffix; 17865 if (NS_FAILED(NodePrincipal()->GetOriginNoSuffix(originNoSuffix))) { 17866 return; 17867 } 17868 nsCOMPtr<nsIURI> uri = NodePrincipal()->GetURI(); 17869 if (!uri) { 17870 return; 17871 } 17872 17873 nsRFPService::MaybeReportFontFingerprinter(GetChannel(), uri, originNoSuffix); 17874 } 17875 17876 bool Document::IsInPrivateBrowsing() const { return mIsInPrivateBrowsing; } 17877 17878 WindowContext* Document::GetWindowContextForPageUseCounters() const { 17879 if (mDisplayDocument) { 17880 // If we are a resource document, then go through it to find the 17881 // top-level document. 17882 return mDisplayDocument->GetWindowContextForPageUseCounters(); 17883 } 17884 17885 if (mOriginalDocument) { 17886 // For static clones (print preview documents), contribute page use counters 17887 // towards the original document. 17888 return mOriginalDocument->GetWindowContextForPageUseCounters(); 17889 } 17890 17891 WindowContext* wc = GetTopLevelWindowContext(); 17892 if (!wc || !wc->GetBrowsingContext()->IsContent()) { 17893 return nullptr; 17894 } 17895 17896 return wc; 17897 } 17898 17899 void Document::UpdateIntersections(TimeStamp aNowTime) { 17900 if (!mIntersectionObservers.IsEmpty()) { 17901 DOMHighResTimeStamp time = 0; 17902 if (nsPIDOMWindowInner* win = GetInnerWindow()) { 17903 if (Performance* perf = win->GetPerformance()) { 17904 time = perf->TimeStampToDOMHighResForRendering(aNowTime); 17905 } 17906 } 17907 for (DOMIntersectionObserver* observer : mIntersectionObservers) { 17908 observer->Update(*this, time); 17909 } 17910 Dispatch(NewRunnableMethod("Document::NotifyIntersectionObservers", this, 17911 &Document::NotifyIntersectionObservers)); 17912 } 17913 } 17914 17915 static void UpdateEffectsOnBrowsingContext(BrowsingContext* aBc, 17916 const IntersectionInput& aInput, 17917 bool aIncludeInactive) { 17918 Element* el = aBc->GetEmbedderElement(); 17919 if (!el) { 17920 return; 17921 } 17922 auto* rb = RemoteBrowser::GetFrom(el); 17923 if (!rb) { 17924 return; 17925 } 17926 const bool isInactiveTop = aBc->IsTop() && !aBc->IsActive(); 17927 nsSubDocumentFrame* subDocFrame = do_QueryFrame(el->GetPrimaryFrame()); 17928 rb->UpdateEffects([&] { 17929 if (isInactiveTop) { 17930 // We don't use the visible rect of top browsers, so if they're inactive 17931 // don't waste time computing it. Note that for OOP iframes, it might seem 17932 // a bit wasteful to bother computing this in background tabs, but: 17933 // * We need it so that child frames know if they're visible or not, in 17934 // case they're preserving layers for example. 17935 // * If we're hidden, our refresh driver is throttled and we don't run 17936 // this code very often anyways. 17937 return EffectsInfo::FullyHidden(); 17938 } 17939 if (MOZ_UNLIKELY(!subDocFrame)) { 17940 // <frame> not inside a <frameset> might not create a subdoc frame, 17941 // for example. 17942 return EffectsInfo::FullyHidden(); 17943 } 17944 const bool inPopup = subDocFrame->HasAnyStateBits(NS_FRAME_IN_POPUP); 17945 Maybe<nsRect> visibleRect; 17946 if (inPopup) { 17947 nsMenuPopupFrame* popup = 17948 do_QueryFrame(nsLayoutUtils::GetDisplayRootFrame(subDocFrame)); 17949 MOZ_ASSERT(popup); 17950 if (!popup || !popup->IsVisibleOrShowing()) { 17951 return EffectsInfo::FullyHidden(); 17952 } 17953 // Be a bit conservative on popups and assume remote frames in there are 17954 // fully visible. 17955 visibleRect = Some(subDocFrame->GetDestRect()); 17956 } else { 17957 const IntersectionOutput output = DOMIntersectionObserver::Intersect( 17958 aInput, *el, DOMIntersectionObserver::BoxToUse::Content); 17959 if (!output.Intersects()) { 17960 // XXX do we want to pass the scale and such down even if out of the 17961 // viewport? 17962 return EffectsInfo::FullyHidden(); 17963 } 17964 visibleRect = subDocFrame->GetVisibleRect(); 17965 if (!visibleRect) { 17966 // If we have no visible rect (e.g., because we are zero-sized) we 17967 // still want to provide the intersection rect in order to get the 17968 // right throttling behavior. 17969 visibleRect.emplace(*output.mIntersectionRect - 17970 output.mTargetRect.TopLeft()); 17971 } 17972 // If we're paginated, the visible rect from the display list might not be 17973 // reasonable, because there can be multiple display items for the frame 17974 // and the rect would be the last one painted. We assume the frame is 17975 // fully visible, lacking something better. 17976 if (subDocFrame->PresContext()->IsPaginated()) { 17977 visibleRect = Some(subDocFrame->GetDestRect()); 17978 } 17979 } 17980 gfx::MatrixScales rasterScale = subDocFrame->GetRasterScale(); 17981 ParentLayerToScreenScale2D transformToAncestorScale = 17982 ParentLayerToParentLayerScale( 17983 subDocFrame->PresShell()->GetCumulativeResolution()) * 17984 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics( 17985 subDocFrame); 17986 return EffectsInfo::VisibleWithinRect(visibleRect, rasterScale, 17987 transformToAncestorScale); 17988 }()); 17989 if (subDocFrame && (!isInactiveTop || aIncludeInactive)) { 17990 if (nsFrameLoader* fl = subDocFrame->FrameLoader()) { 17991 fl->UpdatePositionAndSize(subDocFrame); 17992 } 17993 } 17994 } 17995 17996 void Document::UpdateRemoteFrameEffects(bool aIncludeInactive) { 17997 const IntersectionInput input = 17998 DOMIntersectionObserver::ComputeInputForIframeThrottling(*this); 17999 if (auto* wc = GetWindowContext()) { 18000 for (const RefPtr<BrowsingContext>& child : wc->Children()) { 18001 UpdateEffectsOnBrowsingContext(child, input, aIncludeInactive); 18002 } 18003 } 18004 if (XRE_IsParentProcess()) { 18005 if (auto* bc = GetBrowsingContext(); bc && bc->IsTop()) { 18006 bc->Canonical()->CallOnTopDescendants( 18007 [&](CanonicalBrowsingContext* aDescendant) { 18008 UpdateEffectsOnBrowsingContext(aDescendant, input, 18009 aIncludeInactive); 18010 return CallState::Continue; 18011 }, 18012 CanonicalBrowsingContext::TopDescendantKind::NonNested); 18013 } 18014 } 18015 EnumerateSubDocuments([aIncludeInactive](Document& aDoc) { 18016 aDoc.UpdateRemoteFrameEffects(aIncludeInactive); 18017 return CallState::Continue; 18018 }); 18019 } 18020 18021 void Document::SynchronouslyUpdateRemoteBrowserDimensions( 18022 bool aIncludeInactive) { 18023 FlushPendingNotifications(FlushType::Layout); 18024 UpdateRemoteFrameEffects(aIncludeInactive); 18025 } 18026 18027 void Document::NotifyIntersectionObservers() { 18028 const auto observers = ToTArray<nsTArray<RefPtr<DOMIntersectionObserver>>>( 18029 mIntersectionObservers); 18030 for (const auto& observer : observers) { 18031 // MOZ_KnownLive because the 'observers' array guarantees to keep it 18032 // alive. 18033 MOZ_KnownLive(observer)->Notify(); 18034 } 18035 } 18036 18037 DOMIntersectionObserver& Document::EnsureLazyLoadObserver() { 18038 if (!mLazyLoadObserver) { 18039 mLazyLoadObserver = DOMIntersectionObserver::CreateLazyLoadObserver(*this); 18040 } 18041 return *mLazyLoadObserver; 18042 } 18043 18044 void Document::ObserveForLastRememberedSize(Element& aElement) { 18045 if (NS_WARN_IF(!IsActive())) { 18046 return; 18047 } 18048 mElementsObservedForLastRememberedSize.Insert(&aElement); 18049 } 18050 18051 void Document::UnobserveForLastRememberedSize(Element& aElement) { 18052 mElementsObservedForLastRememberedSize.Remove(&aElement); 18053 } 18054 18055 void Document::UpdateLastRememberedSizes() { 18056 auto shouldRemoveElement = [&](auto* element) { 18057 if (element->GetComposedDoc() != this) { 18058 element->RemoveLastRememberedBSize(); 18059 element->RemoveLastRememberedISize(); 18060 return true; 18061 } 18062 return !element->GetPrimaryFrame(); 18063 }; 18064 18065 for (auto it = mElementsObservedForLastRememberedSize.begin(), 18066 end = mElementsObservedForLastRememberedSize.end(); 18067 it != end; ++it) { 18068 if (shouldRemoveElement(*it)) { 18069 mElementsObservedForLastRememberedSize.Remove(it); 18070 continue; 18071 } 18072 auto* element = *it; 18073 MOZ_ASSERT(element->GetComposedDoc() == this); 18074 nsIFrame* frame = element->GetPrimaryFrame(); 18075 MOZ_ASSERT(frame); 18076 18077 // As for ResizeObserver, skip nodes hidden `content-visibility`. 18078 if (frame->IsHiddenByContentVisibilityOnAnyAncestor()) { 18079 continue; 18080 } 18081 18082 MOZ_ASSERT(!frame->IsLineParticipant() || frame->IsReplaced(), 18083 "Should have unobserved non-replaced inline."); 18084 MOZ_ASSERT(!frame->HidesContent(), 18085 "Should have unobserved element skipping its contents."); 18086 const nsStylePosition* stylePos = frame->StylePosition(); 18087 const WritingMode wm = frame->GetWritingMode(); 18088 bool canUpdateBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto(); 18089 bool canUpdateISize = stylePos->ContainIntrinsicISize(wm).HasAuto(); 18090 MOZ_ASSERT(canUpdateBSize || !element->HasLastRememberedBSize(), 18091 "Should have removed the last remembered block size."); 18092 MOZ_ASSERT(canUpdateISize || !element->HasLastRememberedISize(), 18093 "Should have removed the last remembered inline size."); 18094 MOZ_ASSERT(canUpdateBSize || canUpdateISize, 18095 "Should have unobserved if we can't update any size."); 18096 18097 AutoTArray<LogicalPixelSize, 1> contentSizeList = 18098 ResizeObserver::CalculateBoxSize(element, 18099 ResizeObserverBoxOptions::Content_box, 18100 /* aForceFragmentHandling */ true); 18101 MOZ_ASSERT(!contentSizeList.IsEmpty()); 18102 18103 if (canUpdateBSize) { 18104 float bSize = 0; 18105 for (const auto& current : contentSizeList) { 18106 bSize += current.BSize(); 18107 } 18108 element->SetLastRememberedBSize(bSize); 18109 } 18110 if (canUpdateISize) { 18111 float iSize = 0; 18112 for (const auto& current : contentSizeList) { 18113 iSize = std::max(iSize, current.ISize()); 18114 } 18115 element->SetLastRememberedISize(iSize); 18116 } 18117 } 18118 } 18119 18120 void Document::SetAncestorOriginsList( 18121 nsTArray<nsString>&& aAncestorOriginsList) { 18122 mAncestorOriginsList = std::move(aAncestorOriginsList); 18123 } 18124 18125 Span<const nsString> Document::GetAncestorOriginsList() const { 18126 return mAncestorOriginsList; 18127 } 18128 18129 already_AddRefed<DOMStringList> Document::AncestorOrigins() const { 18130 RefPtr<DOMStringList> list = new DOMStringList(); 18131 for (const auto& origin : mAncestorOriginsList) { 18132 list->Add(origin); 18133 } 18134 return list.forget(); 18135 } 18136 18137 void Document::NotifyLayerManagerRecreated() { 18138 NotifyActivityChanged(); 18139 EnumerateSubDocuments([](Document& aSubDoc) { 18140 aSubDoc.NotifyLayerManagerRecreated(); 18141 return CallState::Continue; 18142 }); 18143 } 18144 18145 XPathEvaluator* Document::XPathEvaluator() { 18146 if (!mXPathEvaluator) { 18147 mXPathEvaluator.reset(new dom::XPathEvaluator(this)); 18148 } 18149 return mXPathEvaluator.get(); 18150 } 18151 18152 already_AddRefed<nsIDocumentEncoder> Document::GetCachedEncoder() { 18153 return mCachedEncoder.forget(); 18154 } 18155 18156 void Document::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) { 18157 mCachedEncoder = aEncoder; 18158 } 18159 18160 nsILoadContext* Document::GetLoadContext() const { return mDocumentContainer; } 18161 18162 nsIDocShell* Document::GetDocShell() const { return mDocumentContainer; } 18163 18164 void Document::SetStateObject(nsIStructuredCloneContainer* scContainer) { 18165 mStateObjectContainer = scContainer; 18166 mCachedStateObject = JS::UndefinedValue(); 18167 mCachedStateObjectValid = false; 18168 } 18169 18170 already_AddRefed<Element> Document::CreateHTMLElement(nsAtom* aTag) { 18171 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 18172 nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML, 18173 ELEMENT_NODE); 18174 MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail"); 18175 18176 nsCOMPtr<Element> element; 18177 DebugOnly<nsresult> rv = 18178 NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(), 18179 mozilla::dom::NOT_FROM_PARSER); 18180 18181 MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail"); 18182 return element.forget(); 18183 } 18184 18185 void AutoWalkBrowsingContextGroup::SuppressBrowsingContext( 18186 BrowsingContext* aContext) { 18187 aContext->PreOrderWalk([&](BrowsingContext* aBC) { 18188 if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) { 18189 if (RefPtr<Document> doc = win->GetExtantDoc()) { 18190 SuppressDocument(doc); 18191 mDocuments.AppendElement(doc); 18192 } 18193 } 18194 }); 18195 } 18196 18197 void AutoWalkBrowsingContextGroup::SuppressBrowsingContextGroup( 18198 BrowsingContextGroup* aGroup) { 18199 for (const auto& bc : aGroup->Toplevels()) { 18200 SuppressBrowsingContext(bc); 18201 } 18202 } 18203 18204 nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc, 18205 SyncOperationBehavior aSyncBehavior) 18206 : mSyncBehavior(aSyncBehavior) { 18207 mMicroTaskLevel = 0; 18208 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) { 18209 mMicroTaskLevel = ccjs->MicroTaskLevel(); 18210 ccjs->SetMicroTaskLevel(0); 18211 ccjs->EnterSyncOperation(); 18212 } 18213 if (aDoc) { 18214 mBrowsingContext = aDoc->GetBrowsingContext(); 18215 if (InputTaskManager::CanSuspendInputEvent()) { 18216 if (auto* bcg = aDoc->GetDocGroup()->GetBrowsingContextGroup()) { 18217 SuppressBrowsingContextGroup(bcg); 18218 } 18219 } else if (mBrowsingContext) { 18220 SuppressBrowsingContext(mBrowsingContext->Top()); 18221 } 18222 if (mBrowsingContext && 18223 mSyncBehavior == SyncOperationBehavior::eSuspendInput && 18224 InputTaskManager::CanSuspendInputEvent()) { 18225 mBrowsingContext->Group()->IncInputEventSuspensionLevel(); 18226 } 18227 } 18228 } 18229 18230 void nsAutoSyncOperation::SuppressDocument(Document* aDoc) { 18231 if (RefPtr<nsGlobalWindowInner> win = 18232 nsGlobalWindowInner::Cast(aDoc->GetInnerWindow())) { 18233 win->GetTimeoutManager()->BeginSyncOperation(); 18234 } 18235 aDoc->SetIsInSyncOperation(true); 18236 } 18237 18238 void nsAutoSyncOperation::UnsuppressDocument(Document* aDoc) { 18239 if (RefPtr<nsGlobalWindowInner> win = 18240 nsGlobalWindowInner::Cast(aDoc->GetInnerWindow())) { 18241 win->GetTimeoutManager()->EndSyncOperation(); 18242 } 18243 aDoc->SetIsInSyncOperation(false); 18244 } 18245 18246 nsAutoSyncOperation::~nsAutoSyncOperation() { 18247 UnsuppressDocuments(); 18248 CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); 18249 if (ccjs) { 18250 ccjs->SetMicroTaskLevel(mMicroTaskLevel); 18251 ccjs->LeaveSyncOperation(); 18252 } 18253 if (mBrowsingContext && 18254 mSyncBehavior == SyncOperationBehavior::eSuspendInput && 18255 InputTaskManager::CanSuspendInputEvent()) { 18256 mBrowsingContext->Group()->DecInputEventSuspensionLevel(); 18257 } 18258 } 18259 18260 void Document::SetIsInSyncOperation(bool aSync) { 18261 if (CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get()) { 18262 ccjs->UpdateMicroTaskSuppressionGeneration(); 18263 } 18264 18265 if (aSync) { 18266 ++mInSyncOperationCount; 18267 } else { 18268 --mInSyncOperationCount; 18269 } 18270 } 18271 18272 gfxUserFontSet* Document::GetUserFontSet() { 18273 if (!mFontFaceSet) { 18274 return nullptr; 18275 } 18276 18277 return mFontFaceSet->GetImpl(); 18278 } 18279 18280 void Document::FlushUserFontSet() { 18281 if (!mFontFaceSetDirty) { 18282 return; 18283 } 18284 18285 mFontFaceSetDirty = false; 18286 18287 if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) { 18288 nsTArray<nsFontFaceRuleContainer> rules; 18289 RefPtr<PresShell> presShell = GetPresShell(); 18290 if (presShell) { 18291 MOZ_ASSERT(mStyleSetFilled); 18292 EnsureStyleSet().AppendFontFaceRules(rules); 18293 } 18294 18295 if (!mFontFaceSet && !rules.IsEmpty()) { 18296 mFontFaceSet = FontFaceSet::CreateForDocument(this); 18297 } 18298 18299 bool changed = false; 18300 if (mFontFaceSet) { 18301 changed = mFontFaceSet->UpdateRules(rules); 18302 } 18303 18304 // We need to enqueue a style change reflow (for later) to 18305 // reflect that we're modifying @font-face rules. (However, 18306 // without a reflow, nothing will happen to start any downloads 18307 // that are needed.) 18308 if (changed && presShell) { 18309 if (nsPresContext* presContext = presShell->GetPresContext()) { 18310 presContext->UserFontSetUpdated(); 18311 } 18312 } 18313 } 18314 } 18315 18316 void Document::MarkUserFontSetDirty() { 18317 if (mFontFaceSetDirty) { 18318 return; 18319 } 18320 mFontFaceSetDirty = true; 18321 if (PresShell* presShell = GetPresShell()) { 18322 presShell->EnsureStyleFlush(); 18323 } 18324 } 18325 18326 FontFaceSet* Document::Fonts() { 18327 if (!mFontFaceSet) { 18328 mFontFaceSet = FontFaceSet::CreateForDocument(this); 18329 FlushUserFontSet(); 18330 } 18331 return mFontFaceSet; 18332 } 18333 18334 void Document::ReportHasScrollLinkedEffect( 18335 const TimeStamp& aTimeStamp, ReportToConsole aReportToConsole /* = Yes */) { 18336 MOZ_ASSERT(!aTimeStamp.IsNull()); 18337 18338 if (!mLastScrollLinkedEffectDetectionTime.IsNull() && 18339 mLastScrollLinkedEffectDetectionTime >= aTimeStamp) { 18340 return; 18341 } 18342 18343 if (aReportToConsole == ReportToConsole::Yes && 18344 mLastScrollLinkedEffectDetectionTime.IsNull()) { 18345 // Report to console just once. 18346 nsContentUtils::ReportToConsole( 18347 nsIScriptError::warningFlag, "Async Pan/Zoom"_ns, this, 18348 nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound3"); 18349 } 18350 18351 mLastScrollLinkedEffectDetectionTime = aTimeStamp; 18352 } 18353 18354 bool Document::HasScrollLinkedEffect() const { 18355 if (nsPresContext* pc = GetPresContext()) { 18356 return mLastScrollLinkedEffectDetectionTime == 18357 pc->RefreshDriver()->MostRecentRefresh(); 18358 } 18359 18360 return false; 18361 } 18362 18363 void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) { 18364 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) { 18365 // Setting has user interaction on a discarded browsing context has 18366 // no effect. 18367 (void)topWc->SetSHEntryHasUserInteraction(aHasInteraction); 18368 } 18369 18370 // For when SHIP is not enabled, we need to get the current entry 18371 // directly from the docshell. 18372 nsIDocShell* docShell = GetDocShell(); 18373 if (docShell) { 18374 nsCOMPtr<nsISHEntry> currentEntry; 18375 bool oshe; 18376 nsresult rv = 18377 docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe); 18378 if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) { 18379 currentEntry->SetHasUserInteraction(aHasInteraction); 18380 } 18381 } 18382 } 18383 18384 bool Document::GetSHEntryHasUserInteraction() { 18385 if (RefPtr<WindowContext> topWc = GetTopLevelWindowContext()) { 18386 return topWc->GetSHEntryHasUserInteraction(); 18387 } 18388 return false; 18389 } 18390 18391 void Document::SetUserHasInteracted() { 18392 MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, 18393 ("Document %p has been interacted by user.", this)); 18394 18395 // We maybe need to update the user-interaction permission. 18396 bool alreadyHadUserInteractionPermission = 18397 ContentBlockingUserInteraction::Exists(NodePrincipal()); 18398 MaybeStoreUserInteractionAsPermission(); 18399 18400 // For purposes of reducing irrelevant session history entries on 18401 // the back button, we annotate entries with whether they had user 18402 // interaction. This is gated on its own flag on the WindowContext 18403 // (instead of mUserHasInteracted) to account for the fact that multiple 18404 // top-level SH entries can be associated with the same document. 18405 // Thus, whenever we create a new SH entry for this document, 18406 // this flag is reset. 18407 if (!GetSHEntryHasUserInteraction()) { 18408 SetSHEntryHasUserInteraction(true); 18409 } 18410 18411 if (mUserHasInteracted) { 18412 return; 18413 } 18414 18415 mUserHasInteracted = true; 18416 18417 if (mChannel) { 18418 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 18419 loadInfo->SetDocumentHasUserInteracted(true); 18420 } 18421 // Tell the parent process about user interaction 18422 if (auto* wgc = GetWindowGlobalChild()) { 18423 wgc->SendUpdateDocumentHasUserInteracted(true); 18424 } 18425 18426 if (alreadyHadUserInteractionPermission) { 18427 MaybeAllowStorageForOpenerAfterUserInteraction(); 18428 } 18429 } 18430 18431 BrowsingContext* Document::GetBrowsingContext() const { 18432 return mDocumentContainer ? mDocumentContainer->GetBrowsingContext() 18433 : nullptr; 18434 } 18435 18436 static void PropagateUserGestureActivationBetweenPiP( 18437 BrowsingContext* currentBC, UserActivation::Modifiers aModifiers) { 18438 // https://wicg.github.io/document-picture-in-picture/#user-activation-propagation 18439 // Monkey patch to activation notification 18440 if (currentBC->Top()->GetIsDocumentPiP()) { 18441 // 5. If we are in a PIP window, give transient activation to the opener 18442 // window 18443 // This means activation in a cross-origin subframe in the PIP window 18444 // will cause the opener to get activation. 18445 RefPtr<BrowsingContext> opener = currentBC->Top()->GetOpener(); 18446 if (!opener) { 18447 return; 18448 } 18449 WindowContext* wc = opener->GetCurrentWindowContext(); 18450 NS_ENSURE_TRUE_VOID(wc); 18451 wc->NotifyUserGestureActivation(aModifiers); 18452 } else { 18453 // 6. Get top-level navigable's last opened PiP window 18454 // this means activation in a cross-origin subframe in the opener will 18455 // cause the PIP window to get activation. 18456 nsPIDOMWindowOuter* outer = currentBC->Top()->GetDOMWindow(); 18457 if (!outer) { 18458 // We don't warn on failure due to the frequency. See bug 2008394. 18459 return; 18460 } 18461 nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow(); 18462 NS_ENSURE_TRUE_VOID(inner); 18463 DocumentPictureInPicture* dpip = inner->GetExtantDocumentPictureInPicture(); 18464 if (!dpip) { 18465 return; 18466 } 18467 nsGlobalWindowInner* pip = dpip->GetWindow(); 18468 if (!pip) { 18469 return; 18470 } 18471 18472 // 7. Give transient activation to the pip window and it's same origin 18473 // descendants 18474 BrowsingContext* pipBC = pip->GetBrowsingContext(); 18475 NS_ENSURE_TRUE_VOID(pipBC); 18476 WindowContext* pipWC = pipBC->GetCurrentWindowContext(); 18477 NS_ENSURE_TRUE_VOID(pipWC); 18478 pipBC->PreOrderWalk([&](BrowsingContext* bc) { 18479 WindowContext* wc = bc->GetCurrentWindowContext(); 18480 if (!wc) { 18481 return; 18482 } 18483 18484 // Check same-origin as current document 18485 WindowGlobalChild* wgc = wc->GetWindowGlobalChild(); 18486 if (!wgc || !wgc->IsSameOriginWith(pipWC)) { 18487 return; 18488 } 18489 18490 wc->NotifyUserGestureActivation(aModifiers); 18491 }); 18492 } 18493 } 18494 18495 void Document::NotifyUserGestureActivation( 18496 UserActivation::Modifiers 18497 aModifiers /* = UserActivation::Modifiers::None() */) { 18498 // https://html.spec.whatwg.org/multipage/interaction.html#activation-notification 18499 // 1. "Assert: document is fully active." 18500 RefPtr<BrowsingContext> currentBC = GetBrowsingContext(); 18501 if (!currentBC) { 18502 return; 18503 } 18504 18505 RefPtr<WindowContext> currentWC = GetWindowContext(); 18506 if (!currentWC) { 18507 return; 18508 } 18509 18510 // 2. "Let windows be « document's relevant global object" 18511 // Instead of assembling a list, we just call notify for wanted windows as we 18512 // find them 18513 currentWC->NotifyUserGestureActivation(aModifiers); 18514 18515 // 3. "...windows with the active window of each of document's ancestor 18516 // navigables." 18517 for (WindowContext* wc = currentWC->GetParentWindowContext(); wc; 18518 wc = wc->GetParentWindowContext()) { 18519 wc->NotifyUserGestureActivation(aModifiers); 18520 } 18521 18522 // 4. "windows with the active window of each of document's descendant 18523 // navigables, filtered to include only those navigables whose active 18524 // document's origin is same origin with document's origin" 18525 currentBC->PreOrderWalk([&](BrowsingContext* bc) { 18526 WindowContext* wc = bc->GetCurrentWindowContext(); 18527 // currentWC has already been notified 18528 if (!wc || wc == currentWC) { 18529 return; 18530 } 18531 18532 // Check same-origin as current document 18533 WindowGlobalChild* wgc = wc->GetWindowGlobalChild(); 18534 if (!wgc || !wgc->IsSameOriginWith(currentWC)) { 18535 return; 18536 } 18537 18538 wc->NotifyUserGestureActivation(aModifiers); 18539 }); 18540 18541 PropagateUserGestureActivationBetweenPiP(currentBC, aModifiers); 18542 18543 // If there has been a user activation, mark the current session history entry 18544 // as having been interacted with. 18545 SetSHEntryHasUserInteraction(true); 18546 } 18547 18548 bool Document::HasBeenUserGestureActivated() { 18549 RefPtr<WindowContext> wc = GetWindowContext(); 18550 return wc && wc->HasBeenUserGestureActivated(); 18551 } 18552 18553 bool Document::ConsumeTextDirectiveUserActivation() { 18554 if (!mChannel) { 18555 return false; 18556 } 18557 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 18558 if (!loadInfo) { 18559 return false; 18560 } 18561 const bool textDirectiveUserActivation = 18562 loadInfo->GetTextDirectiveUserActivation(); 18563 loadInfo->SetTextDirectiveUserActivation(false); 18564 return textDirectiveUserActivation; 18565 } 18566 18567 DOMHighResTimeStamp Document::LastUserGestureTimeStamp() { 18568 if (RefPtr<WindowContext> wc = GetWindowContext()) { 18569 if (nsGlobalWindowInner* innerWindow = wc->GetInnerWindow()) { 18570 if (Performance* perf = innerWindow->GetPerformance()) { 18571 return perf->GetDOMTiming()->TimeStampToDOMHighRes( 18572 wc->GetUserGestureStart()); 18573 } 18574 } 18575 } 18576 18577 NS_WARNING( 18578 "Unable to calculate DOMHighResTimeStamp for LastUserGestureTimeStamp"); 18579 return 0; 18580 } 18581 18582 void Document::ClearUserGestureActivation() { 18583 if (RefPtr<BrowsingContext> bc = GetBrowsingContext()) { 18584 bc = bc->Top(); 18585 bc->PreOrderWalk([&](BrowsingContext* aBC) { 18586 if (WindowContext* windowContext = aBC->GetCurrentWindowContext()) { 18587 windowContext->NotifyResetUserGestureActivation(); 18588 } 18589 }); 18590 } 18591 } 18592 18593 bool Document::HasValidTransientUserGestureActivation() const { 18594 RefPtr<WindowContext> wc = GetWindowContext(); 18595 return wc && wc->HasValidTransientUserGestureActivation(); 18596 } 18597 18598 bool Document::ConsumeTransientUserGestureActivation() { 18599 RefPtr<WindowContext> wc = GetWindowContext(); 18600 return wc && wc->ConsumeTransientUserGestureActivation(); 18601 } 18602 18603 bool Document::GetTransientUserGestureActivationModifiers( 18604 UserActivation::Modifiers* aModifiers) { 18605 RefPtr<WindowContext> wc = GetWindowContext(); 18606 return wc && wc->GetTransientUserGestureActivationModifiers(aModifiers); 18607 } 18608 18609 void Document::SetDocTreeHadMedia() { 18610 RefPtr<WindowContext> topWc = GetTopLevelWindowContext(); 18611 if (topWc && !topWc->IsDiscarded() && !topWc->GetDocTreeHadMedia()) { 18612 MOZ_ALWAYS_SUCCEEDS(topWc->SetDocTreeHadMedia(true)); 18613 } 18614 } 18615 18616 void Document::MaybeAllowStorageForOpenerAfterUserInteraction() { 18617 if (!CookieJarSettings()->GetRejectThirdPartyContexts()) { 18618 return; 18619 } 18620 18621 // This will probably change for project fission, but currently this document 18622 // and the opener are on the same process. In the future, we should make this 18623 // part async. 18624 nsPIDOMWindowInner* inner = GetInnerWindow(); 18625 if (NS_WARN_IF(!inner)) { 18626 return; 18627 } 18628 18629 // Don't trigger navigation heuristic for first-party trackers if the pref 18630 // says so. 18631 if (StaticPrefs:: 18632 privacy_restrict3rdpartystorage_heuristic_exclude_third_party_trackers() && 18633 nsContentUtils::IsFirstPartyTrackingResourceWindow(inner)) { 18634 return; 18635 } 18636 18637 auto* outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow()); 18638 if (NS_WARN_IF(!outer)) { 18639 return; 18640 } 18641 18642 RefPtr<BrowsingContext> openerBC = outer->GetOpenerBrowsingContext(); 18643 if (!openerBC) { 18644 // No opener. 18645 return; 18646 } 18647 18648 // We want to ensure the following check works for both fission mode and 18649 // non-fission mode: 18650 // "If the opener is not a 3rd party and if this window is not a 3rd party 18651 // with respect to the opener, we should not continue." 18652 // 18653 // In non-fission mode, the opener and the opened window are in the same 18654 // process, we can use AntiTrackingUtils::IsThirdPartyWindow to do the check. 18655 // In fission mode, if this window is not a 3rd party with respect to the 18656 // opener, they must be in the same process, so we can still use 18657 // IsThirdPartyWindow(openerInner) to continue to check if the opener is a 3rd 18658 // party. 18659 if (openerBC->IsInProcess()) { 18660 nsCOMPtr<nsPIDOMWindowOuter> outerOpener = openerBC->GetDOMWindow(); 18661 if (NS_WARN_IF(!outerOpener)) { 18662 return; 18663 } 18664 18665 nsCOMPtr<nsPIDOMWindowInner> openerInner = 18666 outerOpener->GetCurrentInnerWindow(); 18667 if (NS_WARN_IF(!openerInner)) { 18668 return; 18669 } 18670 18671 RefPtr<Document> openerDocument = openerInner->GetExtantDoc(); 18672 if (NS_WARN_IF(!openerDocument)) { 18673 return; 18674 } 18675 18676 nsCOMPtr<nsIURI> openerURI = openerDocument->GetDocumentURI(); 18677 if (NS_WARN_IF(!openerURI)) { 18678 return; 18679 } 18680 18681 // If the opener is not a 3rd party and if this window is not 18682 // a 3rd party with respect to the opener, we should not continue. 18683 if (!AntiTrackingUtils::IsThirdPartyWindow(inner, openerURI) && 18684 !AntiTrackingUtils::IsThirdPartyWindow(openerInner, nullptr)) { 18685 return; 18686 } 18687 } 18688 18689 RefPtr<Document> self(this); 18690 WebIdentityHandler* identityHandler = inner->GetOrCreateWebIdentityHandler(); 18691 MOZ_ASSERT(identityHandler); 18692 identityHandler->IsContinuationWindow()->Then( 18693 GetCurrentSerialEventTarget(), __func__, 18694 [self, openerBC](const MozPromise<bool, nsresult, 18695 true>::ResolveOrRejectValue& result) { 18696 if (!result.IsResolve() || !result.ResolveValue()) { 18697 if (XRE_IsParentProcess()) { 18698 (void)StorageAccessAPIHelper::AllowAccessForOnParentProcess( 18699 self->NodePrincipal(), openerBC, 18700 ContentBlockingNotifier::eOpenerAfterUserInteraction); 18701 } else { 18702 (void)StorageAccessAPIHelper::AllowAccessForOnChildProcess( 18703 self->NodePrincipal(), openerBC, 18704 ContentBlockingNotifier::eOpenerAfterUserInteraction); 18705 } 18706 } 18707 }); 18708 } 18709 18710 namespace { 18711 18712 // Documents can stay alive for days. We don't want to update the permission 18713 // value at any user-interaction, and, using a timer triggered any X seconds 18714 // should be good enough. 'X' is taken from 18715 // privacy.userInteraction.document.interval pref. 18716 // We also want to store the user-interaction before shutting down, and, for 18717 // this reason, this class implements nsIAsyncShutdownBlocker interface. 18718 class UserInteractionTimer final : public Runnable, 18719 public nsITimerCallback, 18720 public nsIAsyncShutdownBlocker { 18721 public: 18722 NS_DECL_ISUPPORTS_INHERITED 18723 18724 explicit UserInteractionTimer(Document* aDocument) 18725 : Runnable("UserInteractionTimer"), 18726 mPrincipal(aDocument->NodePrincipal()), 18727 mDocument(aDocument) { 18728 static int32_t userInteractionTimerId = 0; 18729 // Blocker names must be unique. Let's create it now because when needed, 18730 // the document could be already gone. 18731 mBlockerName.AppendPrintf("UserInteractionTimer %d for document %p", 18732 ++userInteractionTimerId, aDocument); 18733 18734 // For ContentBlockingUserInteraction we care about user-interaction stored 18735 // only for top-level documents and documents with access to the Storage 18736 // Access API 18737 if (aDocument->IsTopLevelContentDocument()) { 18738 mShouldRecordContentBlockingUserInteraction = true; 18739 } else { 18740 bool hasSA; 18741 nsresult rv = aDocument->HasStorageAccessSync(hasSA); 18742 mShouldRecordContentBlockingUserInteraction = NS_SUCCEEDED(rv) && hasSA; 18743 } 18744 } 18745 18746 // Runnable interface 18747 18748 NS_IMETHOD 18749 Run() override { 18750 uint32_t interval = 18751 StaticPrefs::privacy_userInteraction_document_interval(); 18752 if (!interval) { 18753 return NS_OK; 18754 } 18755 18756 RefPtr<UserInteractionTimer> self = this; 18757 auto raii = 18758 MakeScopeExit([self] { self->CancelTimerAndStoreUserInteraction(); }); 18759 18760 nsresult rv = NS_NewTimerWithCallback( 18761 getter_AddRefs(mTimer), this, interval * 1000, nsITimer::TYPE_ONE_SHOT); 18762 NS_ENSURE_SUCCESS(rv, NS_OK); 18763 18764 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase(); 18765 NS_ENSURE_TRUE(!!phase, NS_OK); 18766 18767 rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), 18768 __LINE__, u"UserInteractionTimer shutdown"_ns); 18769 NS_ENSURE_SUCCESS(rv, NS_OK); 18770 18771 raii.release(); 18772 return NS_OK; 18773 } 18774 18775 // nsITimerCallback interface 18776 18777 NS_IMETHOD 18778 Notify(nsITimer* aTimer) override { 18779 StoreUserInteraction(); 18780 return NS_OK; 18781 } 18782 18783 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 18784 using nsINamed::GetName; 18785 #endif 18786 18787 // nsIAsyncShutdownBlocker interface 18788 18789 NS_IMETHOD 18790 GetName(nsAString& aName) override { 18791 aName = mBlockerName; 18792 return NS_OK; 18793 } 18794 18795 NS_IMETHOD 18796 BlockShutdown(nsIAsyncShutdownClient* aClient) override { 18797 CancelTimerAndStoreUserInteraction(); 18798 return NS_OK; 18799 } 18800 18801 NS_IMETHOD 18802 GetState(nsIPropertyBag**) override { return NS_OK; } 18803 18804 private: 18805 ~UserInteractionTimer() = default; 18806 18807 void StoreUserInteraction() { 18808 // Remove the shutting down blocker 18809 nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase(); 18810 if (phase) { 18811 phase->RemoveBlocker(this); 18812 } 18813 18814 // If the document is not gone, let's reset its timer flag. 18815 nsCOMPtr<Document> document(mDocument); 18816 if (document) { 18817 if (mShouldRecordContentBlockingUserInteraction) { 18818 ContentBlockingUserInteraction::Observe(mPrincipal); 18819 } 18820 (void)BounceTrackingProtection::RecordUserActivation( 18821 mDocument->GetWindowContext()); 18822 document->ResetUserInteractionTimer(); 18823 } 18824 } 18825 18826 void CancelTimerAndStoreUserInteraction() { 18827 if (mTimer) { 18828 mTimer->Cancel(); 18829 mTimer = nullptr; 18830 } 18831 18832 StoreUserInteraction(); 18833 } 18834 18835 static already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() { 18836 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService(); 18837 NS_ENSURE_TRUE(!!svc, nullptr); 18838 18839 nsCOMPtr<nsIAsyncShutdownClient> phase; 18840 nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase)); 18841 NS_ENSURE_SUCCESS(rv, nullptr); 18842 18843 return phase.forget(); 18844 } 18845 18846 nsCOMPtr<nsIPrincipal> mPrincipal; 18847 WeakPtr<Document> mDocument; 18848 bool mShouldRecordContentBlockingUserInteraction = false; 18849 18850 nsCOMPtr<nsITimer> mTimer; 18851 18852 nsString mBlockerName; 18853 }; 18854 18855 NS_IMPL_ISUPPORTS_INHERITED(UserInteractionTimer, Runnable, nsITimerCallback, 18856 nsIAsyncShutdownBlocker) 18857 18858 } // namespace 18859 18860 void Document::MaybeStoreUserInteractionAsPermission() { 18861 if (!mUserHasInteracted) { 18862 // First interaction, let's store this info now. 18863 (void)BounceTrackingProtection::RecordUserActivation(GetWindowContext()); 18864 18865 // For ContentBlockingUserInteraction we care about user-interaction stored 18866 // only for top-level documents and documents with access to the Storage 18867 // Access API 18868 if (!IsTopLevelContentDocument()) { 18869 bool hasSA; 18870 nsresult rv = HasStorageAccessSync(hasSA); 18871 if (NS_FAILED(rv) || !hasSA) { 18872 return; 18873 } 18874 } 18875 ContentBlockingUserInteraction::Observe(NodePrincipal()); 18876 return; 18877 } 18878 18879 if (mHasUserInteractionTimerScheduled) { 18880 return; 18881 } 18882 18883 nsCOMPtr<nsIRunnable> task = new UserInteractionTimer(this); 18884 nsresult rv = NS_DispatchToCurrentThreadQueue(task.forget(), 2500, 18885 EventQueuePriority::Idle); 18886 if (NS_WARN_IF(NS_FAILED(rv))) { 18887 return; 18888 } 18889 18890 // This value will be reset by the timer. 18891 mHasUserInteractionTimerScheduled = true; 18892 } 18893 18894 void Document::ResetUserInteractionTimer() { 18895 mHasUserInteractionTimerScheduled = false; 18896 } 18897 18898 bool Document::IsExtensionPage() const { 18899 return BasePrincipal::Cast(NodePrincipal())->AddonPolicy(); 18900 } 18901 18902 PermissionDelegateHandler* Document::GetPermissionDelegateHandler() { 18903 if (!mPermissionDelegateHandler) { 18904 mPermissionDelegateHandler = MakeAndAddRef<PermissionDelegateHandler>(this); 18905 } 18906 18907 if (!mPermissionDelegateHandler->Initialize()) { 18908 mPermissionDelegateHandler = nullptr; 18909 } 18910 18911 return mPermissionDelegateHandler; 18912 } 18913 18914 void Document::ScheduleResizeObserversNotification() { 18915 MaybeScheduleRenderingPhases({RenderingPhase::Layout}); 18916 } 18917 18918 static void FlushLayoutForWholeBrowsingContextTree(Document& aDoc, 18919 const ChangesToFlush& aCtf) { 18920 BrowsingContext* bc = aDoc.GetBrowsingContext(); 18921 if (bc && bc->GetExtantDocument() == &aDoc) { 18922 RefPtr<BrowsingContext> top = bc->Top(); 18923 top->PreOrderWalk([aCtf](BrowsingContext* aCur) { 18924 if (Document* doc = aCur->GetExtantDocument()) { 18925 doc->FlushPendingNotifications(aCtf); 18926 } 18927 }); 18928 } else { 18929 // If there is no browsing context, or we're not the current document of the 18930 // browsing context, then we just flush this document itself. 18931 aDoc.FlushPendingNotifications(aCtf); 18932 } 18933 } 18934 18935 // https://html.spec.whatwg.org/#update-the-rendering steps 16 and 17 18936 void Document::DetermineProximityToViewportAndNotifyResizeObservers() { 18937 RefPtr ps = GetPresShell(); 18938 if (!ps) { 18939 return; 18940 } 18941 18942 // Try to do an interruptible reflow if it wouldn't be observable by the page 18943 // in any obvious way. 18944 const bool interruptible = !ps->HasContentVisibilityAutoFrames() && 18945 !HasResizeObservers() && 18946 !HasElementsWithLastRememberedSize(); 18947 ps->ResetWasLastReflowInterrupted(); 18948 18949 // https://github.com/whatwg/html/issues/11210 for this not being in the spec. 18950 ps->UpdateRelevancyOfContentVisibilityAutoFrames(); 18951 18952 // 1. Let resizeObserverDepth be 0. 18953 uint32_t resizeObserverDepth = 0; 18954 bool initialResetOfScrolledIntoViewFlagsDone = false; 18955 const ChangesToFlush ctf( 18956 interruptible ? FlushType::InterruptibleLayout : FlushType::Layout, 18957 /* aFlushAnimations = */ false, /* aUpdateRelevancy = */ false); 18958 18959 bool initialAnchorOverflowDone = false; 18960 18961 // 2. While true: 18962 while (true) { 18963 // 2.1. Recalculate styles and update layout for doc. 18964 if (interruptible) { 18965 ps->FlushPendingNotifications(ctf); 18966 } else { 18967 // The ResizeObserver callbacks functions may do changes in its 18968 // sub-documents or ancestors, so flushing layout for the whole browsing 18969 // context tree makes sure we don't miss anyone. 18970 // 18971 // TODO(emilio): It seems Document::FlushPendingNotifications should be 18972 // able to take care of ancestors... Can we do this less often? 18973 FlushLayoutForWholeBrowsingContextTree(*this, ctf); 18974 } 18975 18976 // Last remembered sizes are recorded "at the time that ResizeObserver 18977 // events are determined and delivered". 18978 // https://drafts.csswg.org/css-sizing-4/#last-remembered 18979 // 18980 // We do it right after layout to make sure sizes are up-to-date. If we do 18981 // it after determining the proximities to viewport of 18982 // 'content-visibility: auto' nodes, and if one of such node ever becomes 18983 // relevant to the user, then we would be incorrectly recording the size 18984 // of its rendering when it was skipping its content. 18985 // 18986 // https://github.com/whatwg/html/issues/11210 for the timing of this. 18987 UpdateLastRememberedSizes(); 18988 18989 const bool evaluateAllFallbacksIfNeeded = !initialAnchorOverflowDone; 18990 initialAnchorOverflowDone = true; 18991 if (AnchorPositioningUtils::TriggerLayoutOnOverflow( 18992 ps, evaluateAllFallbacksIfNeeded)) { 18993 // If any of the anchor positioned items overflow its cb, then we trigger 18994 // a layout for them. If we triggered for any item, we have to restart the 18995 // loop to flush all layouts. 18996 continue; 18997 } 18998 18999 // 2.2. Let hadInitialVisibleContentVisibilityDetermination be false. 19000 // (this is part of "result"). 19001 // 2.3. For each element element with 'auto' used value of 19002 // 'content-visibility' [...] Determine proximity to the viewport for 19003 // element. 19004 auto result = ps->DetermineProximityToViewport(); 19005 if (result.mHadInitialDetermination) { 19006 // 2.4. If hadInitialVisibleContentVisibilityDetermination is true, then 19007 // continue. 19008 continue; 19009 } 19010 if (result.mAnyScrollIntoViewFlag) { 19011 // Not defined in the spec: It's possible that some elements with 19012 // content-visibility: auto were forced to be visible in order to 19013 // perform scrollIntoView() so clear their flags now and restart the 19014 // loop. See https://github.com/w3c/csswg-drafts/issues/9337 19015 ps->ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags(); 19016 ps->ScheduleContentRelevancyUpdate(ContentRelevancyReason::Visible); 19017 if (!initialResetOfScrolledIntoViewFlagsDone) { 19018 initialResetOfScrolledIntoViewFlagsDone = true; 19019 continue; 19020 } 19021 } 19022 19023 // 2.5. Gather active resize observations at depth resizeObserverDepth for 19024 // doc. 19025 GatherAllActiveResizeObservations(resizeObserverDepth); 19026 // 2.6. If doc has active resize observations: [..] steps below 19027 if (!HasAnyActiveResizeObservations()) { 19028 // 2.7. Otherwise, break. 19029 break; 19030 } 19031 // 2.6.1. Set resizeObserverDepth to the result of broadcasting active 19032 // resize observations given doc. 19033 DebugOnly<uint32_t> oldResizeObserverDepth = resizeObserverDepth; 19034 resizeObserverDepth = BroadcastAllActiveResizeObservations(); 19035 NS_ASSERTION(oldResizeObserverDepth < resizeObserverDepth, 19036 "resizeObserverDepth should be getting strictly deeper"); 19037 // 2.6.2. Continue. 19038 } 19039 19040 if (HasAnySkippedResizeObservations()) { 19041 if (nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow()) { 19042 // Per spec, we deliver an error if the document has any skipped 19043 // observations. Also, we re-register via ScheduleNotification(). 19044 RootedDictionary<ErrorEventInit> init(RootingCx()); 19045 init.mMessage.AssignLiteral( 19046 "ResizeObserver loop completed with undelivered notifications."); 19047 init.mBubbles = false; 19048 init.mCancelable = false; 19049 19050 nsEventStatus status = nsEventStatus_eIgnore; 19051 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window); 19052 MOZ_ASSERT(sgo); 19053 if (NS_WARN_IF(sgo->HandleScriptError(init, &status))) { 19054 status = nsEventStatus_eIgnore; 19055 } 19056 } else { 19057 // We don't fire error events at any global for non-window JS on the main 19058 // thread. 19059 } 19060 19061 // We need to deliver pending notifications in next cycle. 19062 ScheduleResizeObserversNotification(); 19063 } 19064 19065 // Step 17: For each doc of docs, if the focused area of doc is not a 19066 // focusable area, then run the focusing steps for doc's viewport, and set 19067 // doc's relevant global object's navigation API's focus changed during 19068 // ongoing navigation to false. 19069 // 19070 // We do it here rather than a separate walk over the docs for convenience, 19071 // and because I don't think there's a strong reason for it to be a separate 19072 // walk altogether, see https://github.com/whatwg/html/issues/11211 19073 const bool fixedUpFocus = ps->FixUpFocus(); 19074 if (fixedUpFocus) { 19075 FlushPendingNotifications(ctf); 19076 } 19077 19078 if (NS_WARN_IF(ps->NeedStyleFlush()) || NS_WARN_IF(ps->NeedLayoutFlush()) || 19079 NS_WARN_IF(fixedUpFocus && ps->NeedsFocusFixUp())) { 19080 ps->EnsureLayoutFlush(); 19081 } 19082 19083 // Inform the FontFaceSet that we ticked, so that it can resolve its ready 19084 // promise if it needs to. See PresShell::MightHavePendingFontLoads. 19085 ps->NotifyFontFaceSetOnRefresh(); 19086 } 19087 19088 void Document::GatherAllActiveResizeObservations(uint32_t aDepth) { 19089 for (ResizeObserver* observer : mResizeObservers) { 19090 observer->GatherActiveObservations(aDepth); 19091 } 19092 } 19093 19094 uint32_t Document::BroadcastAllActiveResizeObservations() { 19095 uint32_t shallowestTargetDepth = std::numeric_limits<uint32_t>::max(); 19096 19097 // Copy the observers as this invokes the callbacks and could register and 19098 // unregister observers at will. 19099 const auto observers = 19100 ToTArray<nsTArray<RefPtr<ResizeObserver>>>(mResizeObservers); 19101 for (const auto& observer : observers) { 19102 // MOZ_KnownLive because 'observers' is guaranteed to keep it 19103 // alive. 19104 // 19105 // This can go away once 19106 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. 19107 uint32_t targetDepth = 19108 MOZ_KnownLive(observer)->BroadcastActiveObservations(); 19109 if (targetDepth < shallowestTargetDepth) { 19110 shallowestTargetDepth = targetDepth; 19111 } 19112 } 19113 19114 return shallowestTargetDepth; 19115 } 19116 19117 bool Document::HasAnySkippedResizeObservations() const { 19118 for (const auto& observer : mResizeObservers) { 19119 if (observer->HasSkippedObservations()) { 19120 return true; 19121 } 19122 } 19123 return false; 19124 } 19125 19126 bool Document::HasAnyActiveResizeObservations() const { 19127 for (const auto& observer : mResizeObservers) { 19128 if (observer->HasActiveObservations()) { 19129 return true; 19130 } 19131 } 19132 return false; 19133 } 19134 19135 void Document::ClearStaleServoData() { 19136 DocumentStyleRootIterator iter(this); 19137 while (Element* root = iter.GetNextStyleRoot()) { 19138 RestyleManager::ClearServoDataFromSubtree(root); 19139 } 19140 } 19141 19142 // https://drafts.csswg.org/css-view-transitions-1/#dom-document-startviewtransition 19143 already_AddRefed<ViewTransition> Document::StartViewTransition( 19144 const ViewTransitionUpdateCallbackOrStartViewTransitionOptions& aOptions) { 19145 // Steps 1-3 19146 19147 nsTArray<RefPtr<nsAtom>> types; 19148 ViewTransitionUpdateCallback* cb = nullptr; 19149 if (aOptions.IsViewTransitionUpdateCallback()) { 19150 cb = &aOptions.GetAsViewTransitionUpdateCallback(); 19151 } else { 19152 MOZ_ASSERT(aOptions.IsStartViewTransitionOptions()); 19153 const auto& options = aOptions.GetAsStartViewTransitionOptions(); 19154 cb = options.mUpdate.get(); 19155 if (!options.mTypes.IsNull()) { 19156 const auto& optionsTypes = options.mTypes.Value(); 19157 types.SetCapacity(optionsTypes.Length()); 19158 for (const auto& type : optionsTypes) { 19159 // TODO(emilio): should probably de-duplicate here. 19160 types.AppendElement(NS_AtomizeMainThread(type)); 19161 } 19162 } 19163 } 19164 RefPtr transition = new ViewTransition(*this, cb, std::move(types)); 19165 if (Hidden()) { 19166 // Step 4: 19167 // 19168 // If document's visibility state is "hidden", then skip transition with an 19169 // "InvalidStateError" DOMException, and return transition. 19170 transition->SkipTransition(SkipTransitionReason::DocumentHidden); 19171 return transition.forget(); 19172 } 19173 if (mActiveViewTransition) { 19174 // Step 5: 19175 // If document's active view transition is not null, then skip that view 19176 // transition with an "AbortError" DOMException in this's relevant Realm. 19177 mActiveViewTransition->SkipTransition( 19178 SkipTransitionReason::ClobberedActiveTransition); 19179 } 19180 // Step 6: Set document's active view transition to transition. 19181 mActiveViewTransition = transition; 19182 19183 // Enable :active-view-transition to allow associated styles to 19184 // be applied during the view transition. 19185 if (auto* root = this->GetRootElement()) { 19186 root->AddStates(ElementState::ACTIVE_VIEW_TRANSITION); 19187 } 19188 19189 EnsureViewTransitionOperationsHappen(); 19190 19191 // Step 7: return transition 19192 return transition.forget(); 19193 } 19194 19195 void Document::ClearActiveViewTransition() { mActiveViewTransition = nullptr; } 19196 19197 void Document::SetRenderingSuppressedForViewTransitions(bool aValue) { 19198 if (mRenderingSuppressedForViewTransitions == aValue) { 19199 return; 19200 } 19201 mRenderingSuppressedForViewTransitions = aValue; 19202 if (aValue) { 19203 return; 19204 } 19205 // We might have missed virtually all rendering steps, ensure we run them. 19206 MaybeScheduleRendering(); 19207 } 19208 19209 void Document::PerformPendingViewTransitionOperations() { 19210 if (mActiveViewTransition && !RenderingSuppressedForViewTransitions()) { 19211 RefPtr activeVT = mActiveViewTransition; 19212 activeVT->PerformPendingOperations(); 19213 } 19214 EnumerateSubDocuments([](Document& aDoc) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { 19215 // Note: EnumerateSubDocuments keeps aDoc alive in a local array, so it's 19216 // fine to use MOZ_KnownLive here. 19217 MOZ_KnownLive(aDoc).PerformPendingViewTransitionOperations(); 19218 return CallState::Continue; 19219 }); 19220 } 19221 19222 void Document::EnsureViewTransitionOperationsHappen() { 19223 MaybeScheduleRenderingPhases({RenderingPhase::ViewTransitionOperations}); 19224 } 19225 19226 Selection* Document::GetSelection(ErrorResult& aRv) { 19227 nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow(); 19228 if (!window) { 19229 return nullptr; 19230 } 19231 19232 if (!window->IsCurrentInnerWindow()) { 19233 return nullptr; 19234 } 19235 19236 return nsGlobalWindowInner::Cast(window)->GetSelection(aRv); 19237 } 19238 19239 void Document::MakeBrowsingContextNonSynthetic() { 19240 if (BrowsingContext* bc = GetBrowsingContext()) { 19241 if (bc->GetIsSyntheticDocumentContainer()) { 19242 (void)bc->SetIsSyntheticDocumentContainer(false); 19243 } 19244 } 19245 } 19246 19247 nsresult Document::HasStorageAccessSync(bool& aHasStorageAccess) { 19248 // Step 1: check if cookie permissions are available or denied to this 19249 // document's principal 19250 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow(); 19251 if (!inner) { 19252 aHasStorageAccess = false; 19253 return NS_OK; 19254 } 19255 Maybe<bool> resultBecauseCookiesApproved = 19256 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI( 19257 CookieJarSettings(), NodePrincipal()); 19258 if (resultBecauseCookiesApproved.isSome()) { 19259 if (resultBecauseCookiesApproved.value()) { 19260 aHasStorageAccess = true; 19261 return NS_OK; 19262 } else { 19263 aHasStorageAccess = false; 19264 return NS_OK; 19265 } 19266 } 19267 19268 // Step 2: Check if the browser settings determine whether or not this 19269 // document has access to its unpartitioned cookies. 19270 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this); 19271 bool isOnThirdPartySkipList = false; 19272 if (mChannel) { 19273 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 19274 isOnThirdPartySkipList = loadInfo->GetStoragePermission() == 19275 nsILoadInfo::StoragePermissionAllowListed; 19276 } 19277 bool isThirdPartyTracker = 19278 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner); 19279 Maybe<bool> resultBecauseBrowserSettings = 19280 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI( 19281 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList, 19282 isThirdPartyTracker); 19283 if (resultBecauseBrowserSettings.isSome()) { 19284 if (resultBecauseBrowserSettings.value()) { 19285 aHasStorageAccess = true; 19286 return NS_OK; 19287 } else { 19288 aHasStorageAccess = false; 19289 return NS_OK; 19290 } 19291 } 19292 19293 // Step 3: Check if the location of this call (embedded, top level, same-site) 19294 // determines if cookies are permitted or not. 19295 Maybe<bool> resultBecauseCallContext = 19296 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this, 19297 false); 19298 if (resultBecauseCallContext.isSome()) { 19299 if (resultBecauseCallContext.value()) { 19300 aHasStorageAccess = true; 19301 return NS_OK; 19302 } else { 19303 aHasStorageAccess = false; 19304 return NS_OK; 19305 } 19306 } 19307 19308 // Step 4: Check if the permissions for this document determine if if has 19309 // access or is denied cookies. 19310 Maybe<bool> resultBecausePreviousPermission = 19311 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI( 19312 this, false); 19313 if (resultBecausePreviousPermission.isSome()) { 19314 if (resultBecausePreviousPermission.value()) { 19315 aHasStorageAccess = true; 19316 return NS_OK; 19317 } else { 19318 aHasStorageAccess = false; 19319 return NS_OK; 19320 } 19321 } 19322 // If you get here, we default to not giving you permission. 19323 aHasStorageAccess = false; 19324 return NS_OK; 19325 } 19326 19327 already_AddRefed<mozilla::dom::Promise> Document::HasStorageAccess( 19328 mozilla::ErrorResult& aRv) { 19329 nsIGlobalObject* global = GetScopeObject(); 19330 if (!global) { 19331 aRv.Throw(NS_ERROR_NOT_AVAILABLE); 19332 return nullptr; 19333 } 19334 19335 RefPtr<Promise> promise = 19336 Promise::Create(global, aRv, Promise::ePropagateUserInteraction); 19337 if (aRv.Failed()) { 19338 return nullptr; 19339 } 19340 19341 if (!IsCurrentActiveDocument()) { 19342 promise->MaybeRejectWithInvalidStateError( 19343 "hasStorageAccess requires an active document"); 19344 return promise.forget(); 19345 } 19346 19347 bool hasStorageAccess; 19348 nsresult rv = HasStorageAccessSync(hasStorageAccess); 19349 if (NS_FAILED(rv)) { 19350 promise->MaybeRejectWithUndefined(); 19351 } else { 19352 promise->MaybeResolve(hasStorageAccess); 19353 } 19354 19355 return promise.forget(); 19356 } 19357 19358 RefPtr<Document::GetContentBlockingEventsPromise> 19359 Document::GetContentBlockingEvents() { 19360 RefPtr<WindowGlobalChild> wgc = GetWindowGlobalChild(); 19361 if (!wgc) { 19362 return nullptr; 19363 } 19364 19365 return wgc->SendGetContentBlockingEvents()->Then( 19366 GetCurrentSerialEventTarget(), __func__, 19367 [](const WindowGlobalChild::GetContentBlockingEventsPromise:: 19368 ResolveOrRejectValue& aValue) { 19369 if (aValue.IsResolve()) { 19370 return Document::GetContentBlockingEventsPromise::CreateAndResolve( 19371 aValue.ResolveValue(), __func__); 19372 } 19373 19374 return Document::GetContentBlockingEventsPromise::CreateAndReject( 19375 false, __func__); 19376 }); 19377 } 19378 19379 StorageAccessAPIHelper::PerformPermissionGrant 19380 Document::CreatePermissionGrantPromise( 19381 nsPIDOMWindowInner* aInnerWindow, nsIPrincipal* aPrincipal, 19382 bool aHasUserInteraction, bool aRequireUserInteraction, 19383 const Maybe<nsCString>& aTopLevelBaseDomain, bool aFrameOnly) { 19384 MOZ_ASSERT(aInnerWindow); 19385 MOZ_ASSERT(aPrincipal); 19386 RefPtr<Document> self(this); 19387 RefPtr<nsPIDOMWindowInner> inner(aInnerWindow); 19388 RefPtr<nsIPrincipal> principal(aPrincipal); 19389 19390 return [inner, self, principal, aHasUserInteraction, aRequireUserInteraction, 19391 aTopLevelBaseDomain, aFrameOnly]() { 19392 RefPtr<StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::Private> 19393 p = new StorageAccessAPIHelper::StorageAccessPermissionGrantPromise:: 19394 Private(__func__); 19395 19396 // Before we prompt, see if we are same-site 19397 if (aFrameOnly) { 19398 nsIChannel* channel = self->GetChannel(); 19399 if (channel) { 19400 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 19401 if (!loadInfo->GetIsThirdPartyContextToTopWindow()) { 19402 p->Resolve(StorageAccessAPIHelper::eAllow, __func__); 19403 return p; 19404 } 19405 } 19406 } 19407 19408 RefPtr<PWindowGlobalChild::GetStorageAccessPermissionPromise> promise; 19409 // Test the permission 19410 MOZ_ASSERT(XRE_IsContentProcess()); 19411 19412 WindowGlobalChild* wgc = inner->GetWindowGlobalChild(); 19413 MOZ_ASSERT(wgc); 19414 19415 promise = wgc->SendGetStorageAccessPermission(true); 19416 MOZ_ASSERT(promise); 19417 promise->Then( 19418 GetCurrentSerialEventTarget(), __func__, 19419 [self, p, inner, principal, aHasUserInteraction, 19420 aRequireUserInteraction, aTopLevelBaseDomain, 19421 aFrameOnly](uint32_t aAction) { 19422 if (aAction == nsIPermissionManager::ALLOW_ACTION) { 19423 p->Resolve(StorageAccessAPIHelper::eAllow, __func__); 19424 return; 19425 } 19426 if (aAction == nsIPermissionManager::DENY_ACTION) { 19427 p->Reject(false, __func__); 19428 return; 19429 } 19430 19431 // We require user activation before conducting a permission request 19432 // See 19433 // https://privacycg.github.io/storage-access/#dom-document-requeststorageaccess 19434 // where we "If has transient activation is false: ..." immediately 19435 // before we "Let permissionState be the result of requesting 19436 // permission to use "storage-access"" from in parallel. 19437 if (!aHasUserInteraction && aRequireUserInteraction) { 19438 // Report an error to the console for this case 19439 nsContentUtils::ReportToConsole( 19440 nsIScriptError::errorFlag, 19441 nsLiteralCString("requestStorageAccess"), self, 19442 nsContentUtils::eDOM_PROPERTIES, 19443 "RequestStorageAccessUserGesture"); 19444 p->Reject(false, __func__); 19445 return; 19446 } 19447 19448 // Create the user prompt 19449 RefPtr<StorageAccessPermissionRequest> sapr = 19450 StorageAccessPermissionRequest::Create( 19451 inner, principal, aTopLevelBaseDomain, aFrameOnly, 19452 // Allow 19453 [p] { 19454 glean::dom::storage_access_api_ui 19455 .EnumGet(glean::dom::StorageAccessApiUiLabel::eAllow) 19456 .Add(); 19457 p->Resolve(StorageAccessAPIHelper::eAllow, __func__); 19458 }, 19459 // Block 19460 [p] { 19461 glean::dom::storage_access_api_ui 19462 .EnumGet(glean::dom::StorageAccessApiUiLabel::eDeny) 19463 .Add(); 19464 p->Reject(false, __func__); 19465 }); 19466 19467 using PromptResult = ContentPermissionRequestBase::PromptResult; 19468 PromptResult pr = sapr->CheckPromptPrefs(); 19469 19470 if (pr == PromptResult::Pending) { 19471 // We're about to show a prompt, record the request attempt 19472 glean::dom::storage_access_api_ui 19473 .EnumGet(glean::dom::StorageAccessApiUiLabel::eRequest) 19474 .Add(); 19475 } 19476 19477 bool isThirdPartyTracker = 19478 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner); 19479 19480 // Try to auto-grant the storage access so the user doesn't see the 19481 // prompt. 19482 self->AutomaticStorageAccessPermissionCanBeGranted( 19483 aHasUserInteraction, isThirdPartyTracker) 19484 ->Then( 19485 GetCurrentSerialEventTarget(), __func__, 19486 // If the autogrant check didn't fail, call this function 19487 [p, pr, sapr, 19488 inner](const Document:: 19489 AutomaticStorageAccessPermissionGrantPromise:: 19490 ResolveOrRejectValue& aValue) -> void { 19491 // Make a copy because we can't modified copy-captured 19492 // lambda variables. 19493 PromptResult pr2 = pr; 19494 19495 // If the user didn't already click "allow" and we can 19496 // autogrant, do that! 19497 bool storageAccessCanBeGrantedAutomatically = 19498 aValue.IsResolve() && aValue.ResolveValue(); 19499 bool autoGrant = false; 19500 if (pr2 == PromptResult::Pending && 19501 storageAccessCanBeGrantedAutomatically) { 19502 pr2 = PromptResult::Granted; 19503 autoGrant = true; 19504 19505 glean::dom::storage_access_api_ui 19506 .EnumGet(glean::dom::StorageAccessApiUiLabel:: 19507 eAllowautomatically) 19508 .Add(); 19509 } 19510 19511 // If we can complete the permission request, do so. 19512 if (pr2 != PromptResult::Pending) { 19513 MOZ_ASSERT_IF(pr2 != PromptResult::Granted, 19514 pr2 == PromptResult::Denied); 19515 if (pr2 == PromptResult::Granted) { 19516 StorageAccessAPIHelper::StorageAccessPromptChoices 19517 choice = StorageAccessAPIHelper::eAllow; 19518 if (autoGrant) { 19519 choice = StorageAccessAPIHelper::eAllowAutoGrant; 19520 } 19521 if (!autoGrant) { 19522 p->Resolve(choice, __func__); 19523 } else { 19524 // We capture sapr here to prevent it from destructing 19525 // before the callbacks complete. 19526 sapr->MaybeDelayAutomaticGrants()->Then( 19527 GetCurrentSerialEventTarget(), __func__, 19528 [p, sapr, choice] { 19529 p->Resolve(choice, __func__); 19530 }, 19531 [p, sapr] { p->Reject(false, __func__); }); 19532 } 19533 return; 19534 } 19535 p->Reject(false, __func__); 19536 return; 19537 } 19538 19539 // If we get here, the auto-decision failed and we need to 19540 // wait for the user prompt to complete. 19541 sapr->RequestDelayedTask( 19542 GetMainThreadSerialEventTarget(), 19543 ContentPermissionRequestBase::DelayedTaskType::Request); 19544 }); 19545 }, 19546 [p](mozilla::ipc::ResponseRejectReason aError) { 19547 p->Reject(false, __func__); 19548 return p; 19549 }); 19550 19551 return p; 19552 }; 19553 } 19554 19555 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess( 19556 mozilla::ErrorResult& aRv) { 19557 nsIGlobalObject* global = GetScopeObject(); 19558 if (!global) { 19559 aRv.Throw(NS_ERROR_NOT_AVAILABLE); 19560 return nullptr; 19561 } 19562 19563 RefPtr<Promise> promise = Promise::Create(global, aRv); 19564 if (aRv.Failed()) { 19565 return nullptr; 19566 } 19567 19568 if (!IsCurrentActiveDocument()) { 19569 promise->MaybeRejectWithInvalidStateError( 19570 "requestStorageAccess requires an active document"); 19571 return promise.forget(); 19572 } 19573 19574 // Get a pointer to the inner window- We need this for convenience sake 19575 RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow(); 19576 if (!inner) { 19577 ConsumeTransientUserGestureActivation(); 19578 promise->MaybeRejectWithNotAllowedError( 19579 "requestStorageAccess not allowed"_ns); 19580 return promise.forget(); 19581 } 19582 19583 // Step 1: Check if the principal calling this has a permission that lets 19584 // them use cookies or forbids them from using cookies. 19585 // This is outside of the spec of the StorageAccess API, but makes the return 19586 // values to have proper semantics. 19587 Maybe<bool> resultBecauseCookiesApproved = 19588 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI( 19589 CookieJarSettings(), NodePrincipal()); 19590 if (resultBecauseCookiesApproved.isSome()) { 19591 if (resultBecauseCookiesApproved.value()) { 19592 promise->MaybeResolveWithUndefined(); 19593 return promise.forget(); 19594 } else { 19595 ConsumeTransientUserGestureActivation(); 19596 promise->MaybeRejectWithNotAllowedError( 19597 "requestStorageAccess not allowed"_ns); 19598 return promise.forget(); 19599 } 19600 } 19601 19602 // Step 2: Check if the browser settings always allow or deny cookies. 19603 // We should always return a resolved promise if the cookieBehavior is ACCEPT. 19604 // This is outside of the spec of the StorageAccess API, but makes the return 19605 // values to have proper semantics. 19606 bool isThirdPartyDocument = AntiTrackingUtils::IsThirdPartyDocument(this); 19607 bool isOnThirdPartySkipList = false; 19608 if (mChannel) { 19609 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 19610 isOnThirdPartySkipList = loadInfo->GetStoragePermission() == 19611 nsILoadInfo::StoragePermissionAllowListed; 19612 } 19613 bool isThirdPartyTracker = 19614 nsContentUtils::IsThirdPartyTrackingResourceWindow(inner); 19615 Maybe<bool> resultBecauseBrowserSettings = 19616 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI( 19617 CookieJarSettings(), isThirdPartyDocument, isOnThirdPartySkipList, 19618 isThirdPartyTracker); 19619 if (resultBecauseBrowserSettings.isSome()) { 19620 if (resultBecauseBrowserSettings.value()) { 19621 promise->MaybeResolveWithUndefined(); 19622 return promise.forget(); 19623 } else { 19624 ConsumeTransientUserGestureActivation(); 19625 promise->MaybeRejectWithNotAllowedError( 19626 "requestStorageAccess not allowed"_ns); 19627 return promise.forget(); 19628 } 19629 } 19630 19631 // Step 3: Check if the Document calling requestStorageAccess has anything to 19632 // gain from storage access. It should be embedded, non-null, etc. 19633 Maybe<bool> resultBecauseCallContext = 19634 StorageAccessAPIHelper::CheckCallingContextDecidesStorageAccessAPI(this, 19635 true); 19636 if (resultBecauseCallContext.isSome()) { 19637 if (resultBecauseCallContext.value()) { 19638 promise->MaybeResolveWithUndefined(); 19639 return promise.forget(); 19640 } else { 19641 ConsumeTransientUserGestureActivation(); 19642 promise->MaybeRejectWithNotAllowedError( 19643 "requestStorageAccess not allowed"_ns); 19644 return promise.forget(); 19645 } 19646 } 19647 19648 // Step 4: Check if we already allowed or denied storage access for this 19649 // document's storage key. 19650 Maybe<bool> resultBecausePreviousPermission = 19651 StorageAccessAPIHelper::CheckExistingPermissionDecidesStorageAccessAPI( 19652 this, true); 19653 if (resultBecausePreviousPermission.isSome()) { 19654 if (resultBecausePreviousPermission.value()) { 19655 promise->MaybeResolveWithUndefined(); 19656 return promise.forget(); 19657 } else { 19658 ConsumeTransientUserGestureActivation(); 19659 promise->MaybeRejectWithNotAllowedError( 19660 "requestStorageAccess not allowed"_ns); 19661 return promise.forget(); 19662 } 19663 } 19664 19665 // Get pointers to some objects that will be used in the async portion 19666 RefPtr<BrowsingContext> bc = GetBrowsingContext(); 19667 RefPtr<nsGlobalWindowOuter> outer = 19668 nsGlobalWindowOuter::Cast(inner->GetOuterWindow()); 19669 if (!outer) { 19670 ConsumeTransientUserGestureActivation(); 19671 promise->MaybeRejectWithNotAllowedError( 19672 "requestStorageAccess not allowed"_ns); 19673 return promise.forget(); 19674 } 19675 RefPtr<Document> self(this); 19676 19677 // Step 5. Start an async call to request storage access. This will either 19678 // perform an automatic decision or notify the user, then perform some follow 19679 // on work changing state to reflect the result of the API. If it resolves, 19680 // the request was granted. If it rejects it was denied. 19681 StorageAccessAPIHelper::RequestStorageAccessAsyncHelper( 19682 this, inner, bc, NodePrincipal(), 19683 self->HasValidTransientUserGestureActivation(), true, true, 19684 ContentBlockingNotifier::eStorageAccessAPI, true) 19685 ->Then( 19686 GetCurrentSerialEventTarget(), __func__, 19687 [inner] { return inner->SaveStorageAccessPermissionGranted(); }, 19688 [] { 19689 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 19690 }) 19691 ->Then( 19692 GetCurrentSerialEventTarget(), __func__, 19693 [promise] { promise->MaybeResolveWithUndefined(); }, 19694 [promise, self] { 19695 self->ConsumeTransientUserGestureActivation(); 19696 promise->MaybeRejectWithNotAllowedError( 19697 "requestStorageAccess not allowed"_ns); 19698 }); 19699 19700 return promise.forget(); 19701 } 19702 19703 already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin( 19704 const nsAString& aThirdPartyOrigin, const bool aRequireUserActivation, 19705 mozilla::ErrorResult& aRv) { 19706 nsIGlobalObject* global = GetScopeObject(); 19707 if (!global) { 19708 aRv.Throw(NS_ERROR_NOT_AVAILABLE); 19709 return nullptr; 19710 } 19711 RefPtr<Promise> promise = Promise::Create(global, aRv); 19712 if (aRv.Failed()) { 19713 return nullptr; 19714 } 19715 19716 // Step 0: Check that we have user activation before proceeding to prevent 19717 // rapid calls to the API to leak information. 19718 if (aRequireUserActivation && !HasValidTransientUserGestureActivation()) { 19719 // Report an error to the console for this case 19720 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, 19721 nsLiteralCString("requestStorageAccess"), 19722 this, nsContentUtils::eDOM_PROPERTIES, 19723 "RequestStorageAccessUserGesture"); 19724 ConsumeTransientUserGestureActivation(); 19725 promise->MaybeRejectWithNotAllowedError( 19726 "requestStorageAccess not allowed"_ns); 19727 return promise.forget(); 19728 } 19729 19730 // Step 1: Check if the provided URI is different-site to this Document 19731 nsCOMPtr<nsIURI> thirdPartyURI; 19732 nsresult rv = NS_NewURI(getter_AddRefs(thirdPartyURI), aThirdPartyOrigin); 19733 if (NS_WARN_IF(NS_FAILED(rv))) { 19734 aRv.Throw(rv); 19735 return nullptr; 19736 } 19737 bool isThirdPartyDocument; 19738 rv = NodePrincipal()->IsThirdPartyURI(thirdPartyURI, &isThirdPartyDocument); 19739 if (NS_WARN_IF(NS_FAILED(rv))) { 19740 aRv.Throw(rv); 19741 return nullptr; 19742 } 19743 Maybe<bool> resultBecauseBrowserSettings = 19744 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI( 19745 CookieJarSettings(), isThirdPartyDocument, false, true); 19746 if (resultBecauseBrowserSettings.isSome()) { 19747 if (resultBecauseBrowserSettings.value()) { 19748 promise->MaybeResolveWithUndefined(); 19749 return promise.forget(); 19750 } 19751 ConsumeTransientUserGestureActivation(); 19752 promise->MaybeRejectWithNotAllowedError( 19753 "requestStorageAccess not allowed"_ns); 19754 return promise.forget(); 19755 } 19756 19757 // Step 2: Check that this Document is same-site to the top, and check that 19758 // we have user activation if we require it. 19759 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper:: 19760 CheckSameSiteCallingContextDecidesStorageAccessAPI( 19761 this, aRequireUserActivation); 19762 if (resultBecauseCallContext.isSome()) { 19763 if (resultBecauseCallContext.value()) { 19764 promise->MaybeResolveWithUndefined(); 19765 return promise.forget(); 19766 } 19767 ConsumeTransientUserGestureActivation(); 19768 promise->MaybeRejectWithNotAllowedError( 19769 "requestStorageAccess not allowed"_ns); 19770 return promise.forget(); 19771 } 19772 19773 // Step 3: Get some useful variables that can be captured by the lambda for 19774 // the asynchronous portion 19775 RefPtr<BrowsingContext> bc = GetBrowsingContext(); 19776 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow(); 19777 if (!inner) { 19778 ConsumeTransientUserGestureActivation(); 19779 promise->MaybeRejectWithNotAllowedError( 19780 "requestStorageAccess not allowed"_ns); 19781 return promise.forget(); 19782 } 19783 RefPtr<nsGlobalWindowOuter> outer = 19784 nsGlobalWindowOuter::Cast(inner->GetOuterWindow()); 19785 if (!outer) { 19786 ConsumeTransientUserGestureActivation(); 19787 promise->MaybeRejectWithNotAllowedError( 19788 "requestStorageAccess not allowed"_ns); 19789 return promise.forget(); 19790 } 19791 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal( 19792 thirdPartyURI, NodePrincipal()->OriginAttributesRef()); 19793 if (!principal) { 19794 ConsumeTransientUserGestureActivation(); 19795 promise->MaybeRejectWithNotAllowedError( 19796 "requestStorageAccess not allowed"_ns); 19797 return promise.forget(); 19798 } 19799 19800 RefPtr<Document> self(this); 19801 bool hasUserActivation = HasValidTransientUserGestureActivation(); 19802 19803 // Consume user activation before entering the async part of this method. 19804 // This prevents usage of other transient activation-gated APIs. 19805 ConsumeTransientUserGestureActivation(); 19806 19807 ContentChild* cc = ContentChild::GetSingleton(); 19808 if (!cc) { 19809 // TODO(bug 1778561): Make this work in non-content processes. 19810 promise->MaybeRejectWithUndefined(); 19811 return promise.forget(); 19812 } 19813 19814 // Step 4a: Start the async part of this function. Check the cookie 19815 // permission, but this can't be done in this process. We needs the cookie 19816 // permission of the URL as if it were embedded on this page, so we need to 19817 // make this check in the ContentParent. 19818 StorageAccessAPIHelper:: 19819 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess( 19820 GetBrowsingContext(), principal) 19821 ->Then( 19822 GetCurrentSerialEventTarget(), __func__, 19823 [inner, thirdPartyURI, bc, principal, hasUserActivation, 19824 aRequireUserActivation, self, 19825 promise](Maybe<bool> cookieResult) { 19826 // Handle the result of the cookie permission check that took 19827 // place in the ContentParent. 19828 if (cookieResult.isSome()) { 19829 if (cookieResult.value()) { 19830 return MozPromise<int, bool, true>::CreateAndResolve( 19831 true, __func__); 19832 } 19833 return MozPromise<int, bool, true>::CreateAndReject(false, 19834 __func__); 19835 } 19836 19837 // Step 4b: Check for the existing storage access permission 19838 nsAutoCString type; 19839 bool ok = AntiTrackingUtils::CreateStoragePermissionKey( 19840 principal, type); 19841 if (!ok) { 19842 return MozPromise<int, bool, true>::CreateAndReject(false, 19843 __func__); 19844 } 19845 if (AntiTrackingUtils::CheckStoragePermission( 19846 self->NodePrincipal(), type, 19847 self->IsInPrivateBrowsing())) { 19848 return MozPromise<int, bool, true>::CreateAndResolve( 19849 true, __func__); 19850 } 19851 19852 // Step 4c: Try to request storage access, either automatically 19853 // or with a user-prompt. This is the part that is async in the 19854 // typical requestStorageAccess function. 19855 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper( 19856 self, inner, bc, principal, hasUserActivation, 19857 aRequireUserActivation, false, 19858 ContentBlockingNotifier:: 19859 ePrivilegeStorageAccessForOriginAPI, 19860 true); 19861 }, 19862 // If the IPC rejects, we should reject our promise here which 19863 // will cause a rejection of the promise we already returned 19864 [promise]() { 19865 return MozPromise<int, bool, true>::CreateAndReject(false, 19866 __func__); 19867 }) 19868 ->Then( 19869 GetCurrentSerialEventTarget(), __func__, 19870 // If the previous handlers resolved, we should reinstate user 19871 // activation and resolve the promise we returned in Step 5. 19872 [self, inner, promise] { 19873 inner->SaveStorageAccessPermissionGranted(); 19874 self->NotifyUserGestureActivation(); 19875 promise->MaybeResolveWithUndefined(); 19876 }, 19877 // If the previous handler rejected, we should reject the promise 19878 // returned by this function. 19879 [promise] { 19880 promise->MaybeRejectWithNotAllowedError( 19881 "requestStorageAccess not allowed"_ns); 19882 }); 19883 19884 // Step 5: While the async stuff is happening, we should return the promise so 19885 // our caller can continue executing. 19886 return promise.forget(); 19887 } 19888 19889 already_AddRefed<Promise> Document::RequestStorageAccessUnderSite( 19890 const nsAString& aSerializedSite, ErrorResult& aRv) { 19891 nsIGlobalObject* global = GetScopeObject(); 19892 if (!global) { 19893 aRv.Throw(NS_ERROR_NOT_AVAILABLE); 19894 return nullptr; 19895 } 19896 RefPtr<Promise> promise = Promise::Create(global, aRv); 19897 if (aRv.Failed()) { 19898 return nullptr; 19899 } 19900 19901 // Check that we have user activation before proceeding to prevent 19902 // rapid calls to the API to leak information. 19903 if (!ConsumeTransientUserGestureActivation()) { 19904 // Report an error to the console for this case 19905 nsContentUtils::ReportToConsole( 19906 nsIScriptError::errorFlag, "requestStorageAccess"_ns, this, 19907 nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture"); 19908 promise->MaybeRejectWithUndefined(); 19909 return promise.forget(); 19910 } 19911 19912 // Check if the provided URI is different-site to this Document 19913 nsCOMPtr<nsIURI> siteURI; 19914 nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite); 19915 if (NS_WARN_IF(NS_FAILED(rv))) { 19916 promise->MaybeRejectWithUndefined(); 19917 return promise.forget(); 19918 } 19919 bool isCrossSiteArgument; 19920 rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument); 19921 if (NS_WARN_IF(NS_FAILED(rv))) { 19922 aRv.Throw(rv); 19923 return nullptr; 19924 } 19925 if (!isCrossSiteArgument) { 19926 promise->MaybeRejectWithUndefined(); 19927 return promise.forget(); 19928 } 19929 19930 // Check if this party has broad cookie permissions. 19931 Maybe<bool> resultBecauseCookiesApproved = 19932 StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI( 19933 CookieJarSettings(), NodePrincipal()); 19934 if (resultBecauseCookiesApproved.isSome()) { 19935 if (resultBecauseCookiesApproved.value()) { 19936 promise->MaybeResolveWithUndefined(); 19937 return promise.forget(); 19938 } 19939 promise->MaybeRejectWithUndefined(); 19940 return promise.forget(); 19941 } 19942 19943 // Check if browser settings preclude this document getting storage 19944 // access under the provided site 19945 Maybe<bool> resultBecauseBrowserSettings = 19946 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI( 19947 CookieJarSettings(), true, false, true); 19948 if (resultBecauseBrowserSettings.isSome()) { 19949 if (resultBecauseBrowserSettings.value()) { 19950 promise->MaybeResolveWithUndefined(); 19951 return promise.forget(); 19952 } 19953 promise->MaybeRejectWithUndefined(); 19954 return promise.forget(); 19955 } 19956 19957 // Check that this Document is same-site to the top 19958 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper:: 19959 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false); 19960 if (resultBecauseCallContext.isSome()) { 19961 if (resultBecauseCallContext.value()) { 19962 promise->MaybeResolveWithUndefined(); 19963 return promise.forget(); 19964 } 19965 promise->MaybeRejectWithUndefined(); 19966 return promise.forget(); 19967 } 19968 19969 nsCOMPtr<nsIPrincipal> principal(NodePrincipal()); 19970 19971 // Test if the permission this is requesting is already set 19972 nsCOMPtr<nsIPrincipal> argumentPrincipal = 19973 BasePrincipal::CreateContentPrincipal( 19974 siteURI, NodePrincipal()->OriginAttributesRef()); 19975 if (!argumentPrincipal) { 19976 ConsumeTransientUserGestureActivation(); 19977 promise->MaybeRejectWithUndefined(); 19978 return promise.forget(); 19979 } 19980 nsCString originNoSuffix; 19981 rv = NodePrincipal()->GetOriginNoSuffix(originNoSuffix); 19982 if (NS_WARN_IF(NS_FAILED(rv))) { 19983 promise->MaybeRejectWithUndefined(); 19984 return promise.forget(); 19985 } 19986 19987 ContentChild* cc = ContentChild::GetSingleton(); 19988 MOZ_ASSERT(cc); 19989 RefPtr<Document> self(this); 19990 cc->SendTestStorageAccessPermission(argumentPrincipal, originNoSuffix) 19991 ->Then( 19992 GetCurrentSerialEventTarget(), __func__, 19993 [promise, siteURI, 19994 self](const ContentChild::TestStorageAccessPermissionPromise:: 19995 ResolveValueType& aResult) { 19996 if (aResult) { 19997 return StorageAccessAPIHelper:: 19998 StorageAccessPermissionGrantPromise::CreateAndResolve( 19999 StorageAccessAPIHelper::eAllow, __func__); 20000 } 20001 // Get a grant for the storage access permission that will be set 20002 // when this is completed in the embedding context 20003 nsCString serializedSite; 20004 nsCOMPtr<nsIEffectiveTLDService> etld = 20005 mozilla::components::EffectiveTLD::Service(); 20006 if (!etld) { 20007 return StorageAccessAPIHelper:: 20008 StorageAccessPermissionGrantPromise::CreateAndReject( 20009 false, __func__); 20010 } 20011 nsresult rv = etld->GetSite(siteURI, serializedSite); 20012 if (NS_FAILED(rv)) { 20013 return StorageAccessAPIHelper:: 20014 StorageAccessPermissionGrantPromise::CreateAndReject( 20015 false, __func__); 20016 } 20017 return self->CreatePermissionGrantPromise( 20018 self->GetInnerWindow(), self->NodePrincipal(), true, true, 20019 Some(serializedSite), false)(); 20020 }, 20021 [](const ContentChild::TestStorageAccessPermissionPromise:: 20022 RejectValueType& aResult) { 20023 return StorageAccessAPIHelper::StorageAccessPermissionGrantPromise:: 20024 CreateAndReject(false, __func__); 20025 }) 20026 ->Then( 20027 GetCurrentSerialEventTarget(), __func__, 20028 [promise, principal, siteURI](int result) { 20029 ContentChild* cc = ContentChild::GetSingleton(); 20030 if (!cc) { 20031 // TODO(bug 1778561): Make this work in non-content processes. 20032 promise->MaybeRejectWithUndefined(); 20033 return; 20034 } 20035 // Set a permission in the parent process that this document wants 20036 // storage access under the argument's site, resolving our returned 20037 // promise on success 20038 cc->SendSetAllowStorageAccessRequestFlag(principal, siteURI) 20039 ->Then( 20040 GetCurrentSerialEventTarget(), __func__, 20041 [promise](bool success) { 20042 if (success) { 20043 promise->MaybeResolveWithUndefined(); 20044 } else { 20045 promise->MaybeRejectWithUndefined(); 20046 } 20047 }, 20048 [promise](mozilla::ipc::ResponseRejectReason reason) { 20049 promise->MaybeRejectWithUndefined(); 20050 }); 20051 }, 20052 [promise](bool result) { promise->MaybeRejectWithUndefined(); }); 20053 20054 // Return the promise that is resolved in the async handler above 20055 return promise.forget(); 20056 } 20057 20058 already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite( 20059 const nsAString& aSerializedOrigin, ErrorResult& aRv) { 20060 nsIGlobalObject* global = GetScopeObject(); 20061 if (!global) { 20062 aRv.Throw(NS_ERROR_NOT_AVAILABLE); 20063 return nullptr; 20064 } 20065 RefPtr<Promise> promise = Promise::Create(global, aRv); 20066 if (aRv.Failed()) { 20067 return nullptr; 20068 } 20069 20070 // Check that the provided URI is different-site to this Document 20071 nsCOMPtr<nsIURI> argumentURI; 20072 nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin); 20073 if (NS_WARN_IF(NS_FAILED(rv))) { 20074 promise->MaybeRejectWithUndefined(); 20075 return promise.forget(); 20076 } 20077 bool isCrossSiteArgument; 20078 rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument); 20079 if (NS_WARN_IF(NS_FAILED(rv))) { 20080 aRv.Throw(rv); 20081 return nullptr; 20082 } 20083 if (!isCrossSiteArgument) { 20084 promise->MaybeRejectWithUndefined(); 20085 return promise.forget(); 20086 } 20087 20088 // Check if browser settings preclude this document getting storage 20089 // access under the provided site 20090 Maybe<bool> resultBecauseBrowserSettings = 20091 StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI( 20092 CookieJarSettings(), true, false, true); 20093 if (resultBecauseBrowserSettings.isSome()) { 20094 if (resultBecauseBrowserSettings.value()) { 20095 promise->MaybeResolveWithUndefined(); 20096 return promise.forget(); 20097 } 20098 promise->MaybeRejectWithUndefined(); 20099 return promise.forget(); 20100 } 20101 20102 // Check that this Document is same-site to the top 20103 Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper:: 20104 CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false); 20105 if (resultBecauseCallContext.isSome()) { 20106 if (resultBecauseCallContext.value()) { 20107 promise->MaybeResolveWithUndefined(); 20108 return promise.forget(); 20109 } 20110 promise->MaybeRejectWithUndefined(); 20111 return promise.forget(); 20112 } 20113 20114 // Create principal of the embedded site requesting storage access 20115 nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal( 20116 argumentURI, NodePrincipal()->OriginAttributesRef()); 20117 if (!principal) { 20118 promise->MaybeRejectWithUndefined(); 20119 return promise.forget(); 20120 } 20121 20122 // Get versions of these objects that we can use in lambdas for callbacks 20123 RefPtr<Document> self(this); 20124 RefPtr<BrowsingContext> bc = GetBrowsingContext(); 20125 nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow(); 20126 20127 // Test that the permission was set by a call to RequestStorageAccessUnderSite 20128 // from a top level document that is same-site with the argument 20129 ContentChild* cc = ContentChild::GetSingleton(); 20130 if (!cc) { 20131 // TODO(bug 1778561): Make this work in non-content processes. 20132 promise->MaybeRejectWithUndefined(); 20133 return promise.forget(); 20134 } 20135 cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI) 20136 ->Then( 20137 GetCurrentSerialEventTarget(), __func__, 20138 [inner, bc, self, principal](bool success) { 20139 if (success) { 20140 // If that resolved with true, check that we don't already have a 20141 // permission that gives cookie access. 20142 return StorageAccessAPIHelper:: 20143 AsyncCheckCookiesPermittedDecidesStorageAccessAPIOnChildProcess( 20144 bc, principal); 20145 } 20146 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject( 20147 NS_ERROR_FAILURE, __func__); 20148 }, 20149 [](mozilla::ipc::ResponseRejectReason reason) { 20150 return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject( 20151 NS_ERROR_FAILURE, __func__); 20152 }) 20153 ->Then( 20154 GetCurrentSerialEventTarget(), __func__, 20155 [inner, bc, principal, self, promise](Maybe<bool> cookieResult) { 20156 // Handle the result of the cookie permission check that took place 20157 // in the ContentParent. 20158 if (cookieResult.isSome()) { 20159 if (cookieResult.value()) { 20160 return StorageAccessAPIHelper:: 20161 StorageAccessPermissionGrantPromise::CreateAndResolve( 20162 StorageAccessAPIHelper::eAllowAutoGrant, __func__); 20163 } 20164 return StorageAccessAPIHelper:: 20165 StorageAccessPermissionGrantPromise::CreateAndReject( 20166 false, __func__); 20167 } 20168 20169 // Check for the existing storage access permission 20170 nsAutoCString type; 20171 bool ok = 20172 AntiTrackingUtils::CreateStoragePermissionKey(principal, type); 20173 if (!ok) { 20174 return StorageAccessAPIHelper:: 20175 StorageAccessPermissionGrantPromise::CreateAndReject( 20176 false, __func__); 20177 } 20178 if (AntiTrackingUtils::CheckStoragePermission( 20179 self->NodePrincipal(), type, self->IsInPrivateBrowsing())) { 20180 return StorageAccessAPIHelper:: 20181 StorageAccessPermissionGrantPromise::CreateAndResolve( 20182 StorageAccessAPIHelper::eAllowAutoGrant, __func__); 20183 } 20184 20185 // Try to request storage access, ignoring the final checks. 20186 // We ignore the final checks because this is where the "grant" 20187 // either by prompt doorhanger or autogrant takes place. We already 20188 // gathered an equivalent grant in requestStorageAccessUnderSite. 20189 return StorageAccessAPIHelper::RequestStorageAccessAsyncHelper( 20190 self, inner, bc, principal, true, true, false, 20191 ContentBlockingNotifier::eStorageAccessAPI, false); 20192 }, 20193 // If the IPC rejects, we should reject our promise here which will 20194 // cause a rejection of the promise we already returned 20195 [promise]() { 20196 return MozPromise<int, bool, true>::CreateAndReject(false, 20197 __func__); 20198 }) 20199 ->Then( 20200 GetCurrentSerialEventTarget(), __func__, 20201 // If the previous handlers resolved, we should reinstate user 20202 // activation and resolve the promise we returned in Step 5. 20203 [self, inner, promise] { 20204 inner->SaveStorageAccessPermissionGranted(); 20205 promise->MaybeResolveWithUndefined(); 20206 }, 20207 // If the previous handler rejected, we should reject the promise 20208 // returned by this function. 20209 [promise] { promise->MaybeRejectWithUndefined(); }); 20210 20211 return promise.forget(); 20212 } 20213 20214 nsTHashSet<RefPtr<WakeLockSentinel>>& Document::ActiveWakeLocks( 20215 WakeLockType aType) { 20216 return mActiveLocks.LookupOrInsert(aType); 20217 } 20218 20219 class UnlockAllWakeLockRunnable final : public Runnable { 20220 public: 20221 UnlockAllWakeLockRunnable(WakeLockType aType, Document* aDoc) 20222 : Runnable("UnlockAllWakeLocks"), mType(aType), mDoc(aDoc) {} 20223 20224 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See 20225 // bug 1535398. 20226 MOZ_CAN_RUN_SCRIPT_BOUNDARY 20227 NS_IMETHOD Run() override { 20228 // Move, as ReleaseWakeLock will try to remove from and possibly allow 20229 // scripts via onrelease to add to document.[[ActiveLocks]]["screen"] 20230 nsCOMPtr<Document> doc = mDoc; 20231 nsTHashSet<RefPtr<WakeLockSentinel>> locks = 20232 std::move(doc->ActiveWakeLocks(mType)); 20233 for (const auto& lock : locks) { 20234 // ReleaseWakeLock runs script, which could release other locks 20235 if (!lock->Released()) { 20236 ReleaseWakeLock(doc, MOZ_KnownLive(lock), mType); 20237 } 20238 } 20239 return NS_OK; 20240 } 20241 20242 protected: 20243 ~UnlockAllWakeLockRunnable() = default; 20244 20245 private: 20246 WakeLockType mType; 20247 nsCOMPtr<Document> mDoc; 20248 }; 20249 20250 void Document::UnlockAllWakeLocks(WakeLockType aType) { 20251 // Perform unlock in a runnable to prevent UnlockAll being MOZ_CAN_RUN_SCRIPT 20252 if (!ActiveWakeLocks(aType).IsEmpty()) { 20253 RefPtr<UnlockAllWakeLockRunnable> runnable = 20254 MakeRefPtr<UnlockAllWakeLockRunnable>(aType, this); 20255 nsresult rv = NS_DispatchToMainThread(runnable); 20256 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); 20257 (void)rv; 20258 } 20259 } 20260 20261 RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise> 20262 Document::AutomaticStorageAccessPermissionCanBeGranted( 20263 bool hasUserActivation, bool isThirdPartyTracker) { 20264 // requestStorageAccessForOrigin may not require user activation. If we don't 20265 // have user activation at this point we should always show the prompt. 20266 if (!hasUserActivation || 20267 !StaticPrefs::privacy_antitracking_enableWebcompat()) { 20268 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve( 20269 false, __func__); 20270 } 20271 20272 if (isThirdPartyTracker && 20273 StaticPrefs:: 20274 dom_storage_access_auto_grants_exclude_third_party_trackers()) { 20275 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve( 20276 false, __func__); 20277 } 20278 20279 if (XRE_IsContentProcess()) { 20280 // In the content process, we need to ask the parent process to compute 20281 // this. The reason is that nsIPermissionManager::GetAllWithTypePrefix() 20282 // isn't accessible in the content process. 20283 ContentChild* cc = ContentChild::GetSingleton(); 20284 MOZ_ASSERT(cc); 20285 20286 return cc->SendAutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()) 20287 ->Then(GetCurrentSerialEventTarget(), __func__, 20288 [](const ContentChild:: 20289 AutomaticStorageAccessPermissionCanBeGrantedPromise:: 20290 ResolveOrRejectValue& aValue) { 20291 if (aValue.IsResolve()) { 20292 return AutomaticStorageAccessPermissionGrantPromise:: 20293 CreateAndResolve(aValue.ResolveValue(), __func__); 20294 } 20295 20296 return AutomaticStorageAccessPermissionGrantPromise:: 20297 CreateAndReject(false, __func__); 20298 }); 20299 } 20300 20301 if (XRE_IsParentProcess()) { 20302 // In the parent process, we can directly compute this. 20303 return AutomaticStorageAccessPermissionGrantPromise::CreateAndResolve( 20304 AutomaticStorageAccessPermissionCanBeGranted(NodePrincipal()), 20305 __func__); 20306 } 20307 20308 return AutomaticStorageAccessPermissionGrantPromise::CreateAndReject( 20309 false, __func__); 20310 } 20311 20312 bool Document::AutomaticStorageAccessPermissionCanBeGranted( 20313 nsIPrincipal* aPrincipal) { 20314 if (!StaticPrefs::dom_storage_access_auto_grants()) { 20315 return false; 20316 } 20317 20318 if (!ContentBlockingUserInteraction::Exists(aPrincipal)) { 20319 return false; 20320 } 20321 20322 nsCOMPtr<nsIBrowserUsage> bu = do_ImportESModule( 20323 "resource:///modules/BrowserUsageTelemetry.sys.mjs", fallible); 20324 if (NS_WARN_IF(!bu)) { 20325 return false; 20326 } 20327 20328 uint32_t uniqueDomainsVisitedInPast24Hours = 0; 20329 nsresult rv = bu->GetUniqueDomainsVisitedInPast24Hours( 20330 &uniqueDomainsVisitedInPast24Hours); 20331 if (NS_WARN_IF(NS_FAILED(rv))) { 20332 return false; 20333 } 20334 20335 Maybe<size_t> maybeOriginsThirdPartyHasAccessTo = 20336 AntiTrackingUtils::CountSitesAllowStorageAccess(aPrincipal); 20337 if (maybeOriginsThirdPartyHasAccessTo.isNothing()) { 20338 return false; 20339 } 20340 size_t originsThirdPartyHasAccessTo = 20341 maybeOriginsThirdPartyHasAccessTo.value(); 20342 20343 // one percent of the number of top-levels origins visited in the current 20344 // session (but not to exceed 24 hours), or the value of the 20345 // dom.storage_access.max_concurrent_auto_grants preference, whichever is 20346 // higher. 20347 size_t maxConcurrentAutomaticGrants = std::max( 20348 std::max(int(std::floor(uniqueDomainsVisitedInPast24Hours / 100)), 20349 StaticPrefs::dom_storage_access_max_concurrent_auto_grants()), 20350 0); 20351 20352 return originsThirdPartyHasAccessTo < maxConcurrentAutomaticGrants; 20353 } 20354 20355 void Document::RecordNavigationTiming(ReadyState aReadyState) { 20356 if (!XRE_IsContentProcess()) { 20357 return; 20358 } 20359 if (!IsTopLevelContentDocument()) { 20360 return; 20361 } 20362 // If we dont have the timing yet (mostly because the doc is still loading), 20363 // get it from docshell. 20364 RefPtr<nsDOMNavigationTiming> timing = mTiming; 20365 if (!timing) { 20366 if (!mDocumentContainer) { 20367 return; 20368 } 20369 timing = mDocumentContainer->GetNavigationTiming(); 20370 if (!timing) { 20371 return; 20372 } 20373 } 20374 TimeStamp startTime = timing->GetNavigationStartTimeStamp(); 20375 switch (aReadyState) { 20376 case READYSTATE_LOADING: 20377 if (!mDOMLoadingSet) { 20378 glean::performance_time::to_dom_loading.AccumulateRawDuration( 20379 TimeStamp::Now() - startTime); 20380 mDOMLoadingSet = true; 20381 } 20382 break; 20383 case READYSTATE_INTERACTIVE: 20384 if (!mDOMInteractiveSet) { 20385 glean::performance_time::dom_interactive.AccumulateRawDuration( 20386 TimeStamp::Now() - startTime); 20387 mDOMInteractiveSet = true; 20388 } 20389 break; 20390 case READYSTATE_COMPLETE: 20391 if (!mDOMCompleteSet) { 20392 glean::performance_time::dom_complete.AccumulateRawDuration( 20393 TimeStamp::Now() - startTime); 20394 mDOMCompleteSet = true; 20395 } 20396 break; 20397 default: 20398 NS_WARNING("Unexpected ReadyState value"); 20399 break; 20400 } 20401 } 20402 20403 void Document::ReportShadowDOMUsage() { 20404 nsPIDOMWindowInner* inner = GetInnerWindow(); 20405 if (NS_WARN_IF(!inner)) { 20406 return; 20407 } 20408 20409 WindowContext* wc = inner->GetWindowContext(); 20410 if (NS_WARN_IF(!wc || wc->IsDiscarded())) { 20411 return; 20412 } 20413 20414 WindowContext* topWc = wc->TopWindowContext(); 20415 if (topWc->GetHasReportedShadowDOMUsage()) { 20416 return; 20417 } 20418 20419 MOZ_ALWAYS_SUCCEEDS(topWc->SetHasReportedShadowDOMUsage(true)); 20420 } 20421 20422 // static 20423 bool Document::StorageAccessSandboxed(uint32_t aSandboxFlags) { 20424 return StaticPrefs::dom_storage_access_enabled() && 20425 (aSandboxFlags & SANDBOXED_STORAGE_ACCESS) != 0; 20426 } 20427 20428 bool Document::StorageAccessSandboxed() const { 20429 return Document::StorageAccessSandboxed(GetSandboxFlags()); 20430 } 20431 20432 bool Document::GetCachedSizes(nsTabSizes* aSizes) { 20433 if (mCachedTabSizeGeneration == 0 || 20434 GetGeneration() != mCachedTabSizeGeneration) { 20435 return false; 20436 } 20437 aSizes->mDom += mCachedTabSizes.mDom; 20438 aSizes->mStyle += mCachedTabSizes.mStyle; 20439 aSizes->mOther += mCachedTabSizes.mOther; 20440 return true; 20441 } 20442 20443 void Document::SetCachedSizes(nsTabSizes* aSizes) { 20444 mCachedTabSizes.mDom = aSizes->mDom; 20445 mCachedTabSizes.mStyle = aSizes->mStyle; 20446 mCachedTabSizes.mOther = aSizes->mOther; 20447 mCachedTabSizeGeneration = GetGeneration(); 20448 } 20449 20450 nsAtom* Document::GetContentLanguageAsAtomForStyle() const { 20451 // Content-Language may be a comma-separated list of language codes, 20452 // in which case the HTML5 spec says to treat it as unknown 20453 if (mContentLanguage && 20454 !nsDependentAtomString(mContentLanguage).Contains(char16_t(','))) { 20455 return GetContentLanguage(); 20456 } 20457 20458 return nullptr; 20459 } 20460 20461 nsAtom* Document::GetLanguageForStyle() const { 20462 if (nsAtom* lang = GetContentLanguageAsAtomForStyle()) { 20463 return lang; 20464 } 20465 return mLanguageFromCharset; 20466 } 20467 20468 void Document::GetContentLanguageForBindings(DOMString& aString) const { 20469 aString.SetKnownLiveAtom(mContentLanguage, DOMString::eTreatNullAsEmpty); 20470 } 20471 20472 const LangGroupFontPrefs* Document::GetFontPrefsForLang( 20473 nsAtom* aLanguage, bool* aNeedsToCache) const { 20474 nsAtom* lang = aLanguage ? aLanguage : mLanguageFromCharset; 20475 return StaticPresData::Get()->GetFontPrefsForLang(lang, aNeedsToCache); 20476 } 20477 20478 void Document::DoCacheAllKnownLangPrefs() { 20479 MOZ_ASSERT(mMayNeedFontPrefsUpdate); 20480 RefPtr<nsAtom> lang = GetLanguageForStyle(); 20481 StaticPresData* data = StaticPresData::Get(); 20482 data->GetFontPrefsForLang(lang ? lang.get() : mLanguageFromCharset); 20483 data->GetFontPrefsForLang(nsGkAtoms::x_math); 20484 // https://bugzilla.mozilla.org/show_bug.cgi?id=1362599#c12 20485 data->GetFontPrefsForLang(nsGkAtoms::Unicode); 20486 for (const auto& key : mLanguagesUsed) { 20487 data->GetFontPrefsForLang(key); 20488 } 20489 mMayNeedFontPrefsUpdate = false; 20490 } 20491 20492 void Document::RecomputeLanguageFromCharset() { 20493 nsAtom* language = mozilla::intl::EncodingToLang::Lookup(mCharacterSet); 20494 20495 if (language == mLanguageFromCharset) { 20496 return; 20497 } 20498 20499 mMayNeedFontPrefsUpdate = true; 20500 mLanguageFromCharset = language; 20501 } 20502 20503 nsICookieJarSettings* Document::CookieJarSettings() { 20504 // If we are here, this is probably a javascript: URL document. In any case, 20505 // we must have a nsCookieJarSettings. Let's create it. 20506 if (!mCookieJarSettings) { 20507 Document* inProcessParent = GetInProcessParentDocument(); 20508 20509 auto shouldInheritFrom = [this](Document* aDoc) { 20510 return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) || 20511 this->NodePrincipal()->GetIsNullPrincipal()); 20512 }; 20513 RefPtr<BrowsingContext> opener = 20514 GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr; 20515 20516 if (inProcessParent) { 20517 mCookieJarSettings = net::CookieJarSettings::Create( 20518 inProcessParent->CookieJarSettings()->GetCookieBehavior(), 20519 mozilla::net::CookieJarSettings::Cast( 20520 inProcessParent->CookieJarSettings()) 20521 ->GetPartitionKey(), 20522 inProcessParent->CookieJarSettings()->GetIsFirstPartyIsolated(), 20523 inProcessParent->CookieJarSettings() 20524 ->GetIsOnContentBlockingAllowList(), 20525 inProcessParent->CookieJarSettings() 20526 ->GetShouldResistFingerprinting()); 20527 20528 // Inherit the fingerprinting random key from the parent. 20529 nsTArray<uint8_t> randomKey; 20530 nsresult rv = inProcessParent->CookieJarSettings() 20531 ->GetFingerprintingRandomizationKey(randomKey); 20532 20533 if (NS_SUCCEEDED(rv)) { 20534 net::CookieJarSettings::Cast(mCookieJarSettings) 20535 ->SetFingerprintingRandomizationKey(randomKey); 20536 } 20537 20538 // Inerit the top level windowContext id from the parent. 20539 net::CookieJarSettings::Cast(mCookieJarSettings) 20540 ->SetTopLevelWindowContextId( 20541 net::CookieJarSettings::Cast(inProcessParent->CookieJarSettings()) 20542 ->GetTopLevelWindowContextId()); 20543 } else if (opener && shouldInheritFrom(opener->GetDocument())) { 20544 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal()); 20545 20546 nsTArray<uint8_t> randomKey; 20547 nsresult rv = opener->GetDocument() 20548 ->CookieJarSettings() 20549 ->GetFingerprintingRandomizationKey(randomKey); 20550 20551 if (NS_SUCCEEDED(rv)) { 20552 net::CookieJarSettings::Cast(mCookieJarSettings) 20553 ->SetFingerprintingRandomizationKey(randomKey); 20554 } 20555 } else { 20556 mCookieJarSettings = net::CookieJarSettings::Create(NodePrincipal()); 20557 20558 if (IsTopLevelContentDocument()) { 20559 net::CookieJarSettings::Cast(mCookieJarSettings) 20560 ->SetTopLevelWindowContextId(InnerWindowID()); 20561 } 20562 } 20563 20564 if (auto* wgc = GetWindowGlobalChild()) { 20565 net::CookieJarSettingsArgs csArgs; 20566 20567 net::CookieJarSettings::Cast(mCookieJarSettings)->Serialize(csArgs); 20568 // Update cookie settings in the parent process 20569 if (!wgc->SendUpdateCookieJarSettings(csArgs)) { 20570 NS_WARNING( 20571 "Failed to update document's cookie jar settings on the " 20572 "WindowGlobalParent"); 20573 } 20574 } 20575 } 20576 20577 return mCookieJarSettings; 20578 } 20579 20580 bool Document::UsingStorageAccess() { 20581 if (WindowContext* wc = GetWindowContext()) { 20582 return wc->GetUsingStorageAccess(); 20583 } 20584 20585 // If we don't yet have a window context, we have to use the decision 20586 // from the Document's Channel's LoadInfo directly. 20587 if (!mChannel) { 20588 return false; 20589 } 20590 20591 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 20592 nsILoadInfo::StoragePermissionState storageAccess = 20593 loadInfo->GetStoragePermission(); 20594 return storageAccess == nsILoadInfo::HasStoragePermission || 20595 storageAccess == nsILoadInfo::StoragePermissionAllowListed; 20596 } 20597 20598 bool Document::IsOn3PCBExceptionList() const { 20599 if (!mChannel) { 20600 return false; 20601 } 20602 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 20603 20604 return loadInfo->GetIsOn3PCBExceptionList(); 20605 } 20606 20607 bool Document::HasStorageAccessPermissionGrantedByAllowList() { 20608 // We only care about if the document gets the storage permission via the 20609 // allow list here. So we don't check the storage access cache in the inner 20610 // window. 20611 20612 if (!mChannel) { 20613 return false; 20614 } 20615 20616 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 20617 return loadInfo->GetStoragePermission() == 20618 nsILoadInfo::StoragePermissionAllowListed; 20619 } 20620 20621 bool Document::InAndroidPipMode() const { 20622 auto* bc = BrowserChild::GetFrom(GetDocShell()); 20623 return bc && bc->InAndroidPipMode(); 20624 } 20625 20626 nsIPrincipal* Document::EffectiveStoragePrincipal() const { 20627 if (!StaticPrefs:: 20628 privacy_partition_always_partition_third_party_non_cookie_storage()) { 20629 return EffectiveCookiePrincipal(); 20630 } 20631 20632 nsPIDOMWindowInner* inner = GetInnerWindow(); 20633 if (!inner) { 20634 return NodePrincipal(); 20635 } 20636 20637 // Return our cached storage principal if one exists. 20638 if (mActiveStoragePrincipal) { 20639 return mActiveStoragePrincipal; 20640 } 20641 20642 // Calling StorageAllowedForDocument will notify the ContentBlockLog. This 20643 // loads TrackingDBService.sys.mjs, making us potentially 20644 // fail // browser/base/content/test/performance/browser_startup.js. To avoid 20645 // that, we short-circuit the check here by allowing storage access to system 20646 // and addon principles, avoiding the test-failure. 20647 nsIPrincipal* principal = NodePrincipal(); 20648 if (principal && (principal->IsSystemPrincipal() || 20649 principal->GetIsAddonOrExpandedAddonPrincipal())) { 20650 return mActiveStoragePrincipal = NodePrincipal(); 20651 } 20652 20653 auto cookieJarSettings = const_cast<Document*>(this)->CookieJarSettings(); 20654 if (cookieJarSettings->GetIsOnContentBlockingAllowList()) { 20655 return mActiveStoragePrincipal = NodePrincipal(); 20656 } 20657 20658 // We use the lower-level ContentBlocking API here to ensure this 20659 // check doesn't send notifications. 20660 uint32_t rejectedReason = 0; 20661 if (ShouldAllowAccessFor(inner, GetDocumentURI(), false, &rejectedReason)) { 20662 return mActiveStoragePrincipal = NodePrincipal(); 20663 } 20664 20665 // Let's use the storage principal only if we need to partition the cookie 20666 // jar. When the permission is granted, access will be different and the 20667 // normal principal will be used. 20668 if (ShouldPartitionStorage(rejectedReason) && 20669 !StoragePartitioningEnabled( 20670 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) { 20671 return mActiveStoragePrincipal = NodePrincipal(); 20672 } 20673 20674 (void)NS_WARN_IF(NS_FAILED(StoragePrincipalHelper::GetPrincipal( 20675 nsGlobalWindowInner::Cast(inner), 20676 StoragePrincipalHelper::eForeignPartitionedPrincipal, 20677 getter_AddRefs(mActiveStoragePrincipal)))); 20678 return mActiveStoragePrincipal; 20679 } 20680 20681 nsIPrincipal* Document::EffectiveCookiePrincipal() const { 20682 nsPIDOMWindowInner* inner = GetInnerWindow(); 20683 if (!inner) { 20684 return NodePrincipal(); 20685 } 20686 20687 // Return our cached storage principal if one exists. 20688 // 20689 // Handle special case where privacy_partition_always_partition_third_party 20690 // _non_cookie_storage is disabled and the loading document has 20691 // StorageAccess. The pref will lead to WindowGlobalChild::OnNewDocument 20692 // setting the documents StoragePrincipal on the parent to the documents 20693 // EffectiveCookiePrincipal. Since this happens before the WindowContext, 20694 // including possible StorageAccess, is set the PartitonedPrincipal will be 20695 // selected and cached. Since no change of permission occured it won't be 20696 // updated later. Avoid this by not using a cached PartitionedPrincipal if 20697 // the pref is disabled, this should rarely happen since the pref defaults to 20698 // true. See Bug 1899570. 20699 if (mActiveCookiePrincipal && 20700 (StaticPrefs:: 20701 privacy_partition_always_partition_third_party_non_cookie_storage() || 20702 mActiveCookiePrincipal != mPartitionedPrincipal)) { 20703 return mActiveCookiePrincipal; 20704 } 20705 20706 // We use the lower-level ContentBlocking API here to ensure this 20707 // check doesn't send notifications. 20708 uint32_t rejectedReason = 0; 20709 if (ShouldAllowAccessFor(inner, GetDocumentURI(), true, &rejectedReason)) { 20710 return mActiveCookiePrincipal = NodePrincipal(); 20711 } 20712 20713 // Let's use the storage principal only if we need to partition the cookie 20714 // jar. When the permission is granted, access will be different and the 20715 // normal principal will be used. 20716 if (ShouldPartitionStorage(rejectedReason) && 20717 !StoragePartitioningEnabled( 20718 rejectedReason, const_cast<Document*>(this)->CookieJarSettings())) { 20719 return mActiveCookiePrincipal = NodePrincipal(); 20720 } 20721 20722 return mActiveCookiePrincipal = mPartitionedPrincipal; 20723 } 20724 20725 nsIPrincipal* Document::GetPrincipalForPrefBasedHacks() const { 20726 // If the document is sandboxed document or data: document, we should 20727 // get URI of the parent document. 20728 for (const Document* document = this; 20729 document && document->IsContentDocument(); 20730 document = document->GetInProcessParentDocument()) { 20731 // The document URI may be about:blank even if it comes from actual web 20732 // site. Therefore, we need to check the URI of its principal. 20733 nsIPrincipal* principal = document->NodePrincipal(); 20734 if (principal->GetIsNullPrincipal()) { 20735 continue; 20736 } 20737 return principal; 20738 } 20739 return nullptr; 20740 } 20741 20742 void Document::SetInitialStatus(InitialStatus aStatus) { 20743 mInitialStatus = aStatus; 20744 20745 if (aStatus == InitialStatus::IsInitialUncommitted) { 20746 // Set readyState to complete silently. 20747 mReadyState = READYSTATE_COMPLETE; 20748 mSetCompleteAfterDOMContentLoaded = false; 20749 mSynchronousDOMContentLoaded = true; 20750 } else if (aStatus == InitialStatus::IsInitialButExplicitlyOpened) { 20751 mSynchronousDOMContentLoaded = false; 20752 } 20753 20754 // Asynchronously tell the parent process that we are, or are no longer, the 20755 // initial document. This happens async. 20756 if (auto* wgc = GetWindowGlobalChild()) { 20757 wgc->SendSetIsInitialDocument(IsInitialDocument()); 20758 } 20759 } 20760 20761 void Document::BeginInitialAboutBlankLoadCompleting(nsIChannel* aChannel) { 20762 MOZ_ASSERT(aChannel); 20763 SetInitialStatus(InitialStatus::IsInitialCommitted); 20764 if (auto* wgc = GetWindowGlobalChild()) { 20765 wgc->SendCommitToInitialDocument(); 20766 } 20767 mInitialAboutBlankLoadCompleting = true; 20768 mChannel = aChannel; 20769 mChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); 20770 20771 // This is the condition under which we would usually set 20772 // mMaybeServiceWorkerControlled in SetScriptGlobalObject. 20773 MOZ_ASSERT(mDocumentContainer && mScriptGlobalObject, 20774 "Should have document container and script global"); 20775 mMaybeServiceWorkerControlled = true; 20776 } 20777 20778 // static 20779 void Document::AddToplevelLoadingDocument(Document* aDoc) { 20780 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument()); 20781 20782 if (!XRE_IsContentProcess()) { 20783 return; 20784 } 20785 20786 // Start the JS execution timer. 20787 { 20788 AutoJSContext cx; 20789 if (static_cast<JSContext*>(cx)) { 20790 JS::SetMeasuringExecutionTimeEnabled(cx, true); 20791 } 20792 } 20793 20794 // Currently we're interested in foreground documents only, so bail out early. 20795 if (aDoc->IsInBackgroundWindow()) { 20796 return; 20797 } 20798 20799 if (!sLoadingForegroundTopLevelContentDocument) { 20800 sLoadingForegroundTopLevelContentDocument = new AutoTArray<Document*, 8>(); 20801 mozilla::ipc::IdleSchedulerChild* idleScheduler = 20802 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler(); 20803 if (idleScheduler) { 20804 idleScheduler->SendRunningPrioritizedOperation(); 20805 } 20806 } 20807 if (!sLoadingForegroundTopLevelContentDocument->Contains(aDoc)) { 20808 sLoadingForegroundTopLevelContentDocument->AppendElement(aDoc); 20809 } 20810 } 20811 20812 // static 20813 void Document::RemoveToplevelLoadingDocument(Document* aDoc) { 20814 MOZ_ASSERT(aDoc && aDoc->IsTopLevelContentDocument()); 20815 if (sLoadingForegroundTopLevelContentDocument) { 20816 sLoadingForegroundTopLevelContentDocument->RemoveElement(aDoc); 20817 if (sLoadingForegroundTopLevelContentDocument->IsEmpty()) { 20818 delete sLoadingForegroundTopLevelContentDocument; 20819 sLoadingForegroundTopLevelContentDocument = nullptr; 20820 20821 mozilla::ipc::IdleSchedulerChild* idleScheduler = 20822 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler(); 20823 if (idleScheduler) { 20824 idleScheduler->SendPrioritizedOperationDone(); 20825 } 20826 } 20827 } 20828 20829 // Stop the JS execution timer once the page is loaded. 20830 { 20831 AutoJSContext cx; 20832 if (static_cast<JSContext*>(cx)) { 20833 JS::SetMeasuringExecutionTimeEnabled(cx, false); 20834 } 20835 } 20836 } 20837 20838 ColorScheme Document::DefaultColorScheme() const { 20839 return LookAndFeel::ColorSchemeForStyle(*this, {GetColorSchemeBits()}); 20840 } 20841 20842 ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const { 20843 if (ShouldResistFingerprinting(RFPTarget::CSSPrefersColorScheme) && 20844 aIgnoreRFP == IgnoreRFP::No) { 20845 return ColorScheme::Light; 20846 } 20847 20848 if (nsPresContext* pc = GetPresContext()) { 20849 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) { 20850 return *scheme; 20851 } 20852 } 20853 20854 return PreferenceSheet::PrefsFor(*this).mColorScheme; 20855 } 20856 20857 bool Document::HasRecentlyStartedForegroundLoads() { 20858 if (!sLoadingForegroundTopLevelContentDocument) { 20859 return false; 20860 } 20861 20862 for (size_t i = 0; i < sLoadingForegroundTopLevelContentDocument->Length(); 20863 ++i) { 20864 Document* doc = sLoadingForegroundTopLevelContentDocument->ElementAt(i); 20865 // A page loaded in foreground could be in background now. 20866 if (!doc->IsInBackgroundWindow()) { 20867 nsPIDOMWindowInner* win = doc->GetInnerWindow(); 20868 if (win) { 20869 Performance* perf = win->GetPerformance(); 20870 if (perf && 20871 perf->Now() < StaticPrefs::page_load_deprioritization_period()) { 20872 return true; 20873 } 20874 } 20875 } 20876 } 20877 20878 // Didn't find any loading foreground documents, just clear the array. 20879 delete sLoadingForegroundTopLevelContentDocument; 20880 sLoadingForegroundTopLevelContentDocument = nullptr; 20881 20882 mozilla::ipc::IdleSchedulerChild* idleScheduler = 20883 mozilla::ipc::IdleSchedulerChild::GetMainThreadIdleScheduler(); 20884 if (idleScheduler) { 20885 idleScheduler->SendPrioritizedOperationDone(); 20886 } 20887 return false; 20888 } 20889 20890 void Document::AddPendingFrameStaticClone(nsFrameLoaderOwner* aElement, 20891 nsFrameLoader* aStaticCloneOf) { 20892 PendingFrameStaticClone* clone = mPendingFrameStaticClones.AppendElement(); 20893 clone->mElement = aElement; 20894 clone->mStaticCloneOf = aStaticCloneOf; 20895 } 20896 20897 bool Document::ShouldAvoidNativeTheme() const { 20898 return !IsInChromeDocShell() || XRE_IsContentProcess(); 20899 } 20900 20901 bool Document::UseRegularPrincipal() const { 20902 return EffectiveStoragePrincipal() == NodePrincipal(); 20903 } 20904 20905 bool Document::HasThirdPartyChannel() { 20906 nsCOMPtr<nsIChannel> channel = GetChannel(); 20907 if (channel) { 20908 // We assume that the channel is a third-party by default. 20909 bool thirdParty = true; 20910 20911 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = 20912 components::ThirdPartyUtil::Service(); 20913 if (!thirdPartyUtil) { 20914 return thirdParty; 20915 } 20916 20917 // Check that if the channel is a third-party to its parent. 20918 nsresult rv = 20919 thirdPartyUtil->IsThirdPartyChannel(channel, nullptr, &thirdParty); 20920 if (NS_FAILED(rv)) { 20921 // Assume third-party in case of failure 20922 thirdParty = true; 20923 } 20924 20925 return thirdParty; 20926 } 20927 20928 if (mParentDocument) { 20929 return mParentDocument->HasThirdPartyChannel(); 20930 } 20931 20932 return false; 20933 } 20934 20935 bool Document::IsLikelyContentInaccessibleTopLevelAboutBlank() const { 20936 if (!mDocumentURI || !NS_IsAboutBlank(mDocumentURI)) { 20937 return false; 20938 } 20939 // FIXME(emilio): This is not quite edge-case free. See bug 1860098. 20940 // 20941 // For stuff in frames, that makes our per-document telemetry probes not 20942 // really reliable but doesn't affect the correctness of our page probes, so 20943 // it's not too terrible. 20944 BrowsingContext* bc = GetBrowsingContext(); 20945 return bc && bc->IsTop() && !bc->GetTopLevelCreatedByWebContent(); 20946 } 20947 20948 bool Document::ShouldIncludeInTelemetry() const { 20949 if (!IsContentDocument() && !IsResourceDoc()) { 20950 return false; 20951 } 20952 20953 if (IsLikelyContentInaccessibleTopLevelAboutBlank()) { 20954 return false; 20955 } 20956 20957 nsIPrincipal* prin = NodePrincipal(); 20958 // TODO(emilio): Should this use GetIsContentPrincipal() + 20959 // GetPrecursorPrincipal() instead (accounting for add-ons separately)? 20960 return !(prin->GetIsAddonOrExpandedAddonPrincipal() || 20961 prin->IsSystemPrincipal() || prin->SchemeIs("about") || 20962 prin->SchemeIs("chrome") || prin->SchemeIs("resource")); 20963 } 20964 20965 void Document::GetConnectedShadowRoots( 20966 nsTArray<RefPtr<ShadowRoot>>& aOut) const { 20967 AppendToArray(aOut, mComposedShadowRoots); 20968 } 20969 20970 void Document::AddMediaElementWithMSE() { 20971 if (mMediaElementWithMSECount++ == 0) { 20972 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) { 20973 wgc->BlockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT); 20974 } 20975 } 20976 } 20977 20978 void Document::RemoveMediaElementWithMSE() { 20979 MOZ_ASSERT(mMediaElementWithMSECount > 0); 20980 if (--mMediaElementWithMSECount == 0) { 20981 if (WindowGlobalChild* wgc = GetWindowGlobalChild()) { 20982 wgc->UnblockBFCacheFor(BFCacheStatus::CONTAINS_MSE_CONTENT); 20983 } 20984 } 20985 } 20986 20987 void Document::UnregisterFromMemoryReportingForDataDocument() { 20988 if (!mAddedToMemoryReportingAsDataDocument) { 20989 return; 20990 } 20991 mAddedToMemoryReportingAsDataDocument = false; 20992 nsIGlobalObject* global = GetScopeObject(); 20993 if (global) { 20994 if (nsPIDOMWindowInner* win = global->GetAsInnerWindow()) { 20995 nsGlobalWindowInner::Cast(win)->UnregisterDataDocumentForMemoryReporting( 20996 this); 20997 } 20998 } 20999 } 21000 void Document::OOPChildLoadStarted(BrowserBridgeChild* aChild) { 21001 MOZ_DIAGNOSTIC_ASSERT(!mOOPChildrenLoading.Contains(aChild)); 21002 mOOPChildrenLoading.AppendElement(aChild); 21003 if (mOOPChildrenLoading.Length() == 1) { 21004 // Let's block unload so that we're blocked from going into the BFCache 21005 // until the child has actually notified us that it has done loading. 21006 BlockOnload(); 21007 } 21008 } 21009 21010 void Document::OOPChildLoadDone(BrowserBridgeChild* aChild) { 21011 // aChild will not be in the list if nsDocLoader::Stop() was called, since 21012 // that clears mOOPChildrenLoading. It also dispatches the 'load' event, 21013 // so we don't need to call DocLoaderIsEmpty in that case. 21014 if (mOOPChildrenLoading.RemoveElement(aChild)) { 21015 if (mOOPChildrenLoading.IsEmpty()) { 21016 UnblockOnload(false); 21017 } 21018 RefPtr<nsDocLoader> docLoader(mDocumentContainer); 21019 if (docLoader) { 21020 docLoader->OOPChildrenLoadingIsEmpty(); 21021 } 21022 } 21023 } 21024 21025 void Document::ClearOOPChildrenLoading() { 21026 nsTArray<const BrowserBridgeChild*> oopChildrenLoading; 21027 mOOPChildrenLoading.SwapElements(oopChildrenLoading); 21028 if (!oopChildrenLoading.IsEmpty()) { 21029 UnblockOnload(false); 21030 } 21031 } 21032 21033 bool Document::MayHaveDOMActivateListeners() const { 21034 if (nsPIDOMWindowInner* inner = GetInnerWindow()) { 21035 return inner->HasDOMActivateEventListeners(); 21036 } 21037 21038 // If we can't get information from the window object, default to true. 21039 return true; 21040 } 21041 21042 HighlightRegistry& Document::HighlightRegistry() { 21043 if (!mHighlightRegistry) { 21044 mHighlightRegistry = MakeRefPtr<class HighlightRegistry>(this); 21045 } 21046 return *mHighlightRegistry; 21047 } 21048 21049 FragmentDirective* Document::FragmentDirective() { 21050 if (!mFragmentDirective) { 21051 mFragmentDirective = MakeRefPtr<class FragmentDirective>(this); 21052 } 21053 return mFragmentDirective; 21054 } 21055 21056 RadioGroupContainer& Document::OwnedRadioGroupContainer() { 21057 if (!mRadioGroupContainer) { 21058 mRadioGroupContainer = MakeUnique<RadioGroupContainer>(); 21059 } 21060 return *mRadioGroupContainer; 21061 } 21062 21063 void Document::UpdateHiddenByContentVisibilityForAnimations() { 21064 mTimelinesController.UpdateHiddenByContentVisibility(); 21065 } 21066 21067 void Document::SetAllowDeclarativeShadowRoots( 21068 bool aAllowDeclarativeShadowRoots) { 21069 mAllowDeclarativeShadowRoots = aAllowDeclarativeShadowRoots; 21070 } 21071 21072 bool Document::AllowsDeclarativeShadowRoots() const { 21073 return mAllowDeclarativeShadowRoots; 21074 } 21075 21076 static already_AddRefed<Document> CreateHTMLDocument(GlobalObject& aGlobal, 21077 ErrorResult& aError) { 21078 nsCOMPtr<nsIURI> uri; 21079 aError = NS_NewURI(getter_AddRefs(uri), "about:blank"); 21080 if (aError.Failed()) { 21081 return nullptr; 21082 } 21083 21084 nsCOMPtr<Document> doc; 21085 aError = 21086 NS_NewHTMLDocument(getter_AddRefs(doc), aGlobal.GetSubjectPrincipal(), 21087 aGlobal.GetSubjectPrincipal(), LoadedAsData::AsData); 21088 if (aError.Failed()) { 21089 return nullptr; 21090 } 21091 21092 doc->SetAllowDeclarativeShadowRoots(true); 21093 doc->SetDocumentURI(uri); 21094 21095 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject = 21096 do_QueryInterface(aGlobal.GetAsSupports()); 21097 doc->SetScriptHandlingObject(scriptHandlingObject); 21098 doc->SetDocumentCharacterSet(UTF_8_ENCODING); 21099 21100 return doc.forget(); 21101 } 21102 21103 /* static */ 21104 already_AddRefed<Document> Document::ParseHTMLUnsafe( 21105 GlobalObject& aGlobal, const TrustedHTMLOrString& aHTML, 21106 const SetHTMLUnsafeOptions& aOptions, nsIPrincipal* aSubjectPrincipal, 21107 ErrorResult& aError) { 21108 // Step 1. Let compliantHTML be the result of invoking the Get Trusted Type 21109 // compliant string algorithm with TrustedHTML, this’s relevant global object, 21110 // html, "Document parseHTMLUnsafe", and "script". 21111 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 21112 constexpr nsLiteralString sink = u"Document parseHTMLUnsafe"_ns; 21113 Maybe<nsAutoString> compliantStringHolder; 21114 const nsAString* compliantString = 21115 TrustedTypeUtils::GetTrustedTypesCompliantString( 21116 aHTML, sink, kTrustedTypesOnlySinkGroup, *global, aSubjectPrincipal, 21117 compliantStringHolder, aError); 21118 if (aError.Failed()) { 21119 return nullptr; 21120 } 21121 21122 // TODO: Always initialize the sanitizer. 21123 bool sanitize = aOptions.mSanitizer.WasPassed(); 21124 21125 // Step 2. Let document be a new Document, whose content type is "text/html". 21126 // Step 3. Set document’s allow declarative shadow roots to true. 21127 RefPtr<Document> doc = CreateHTMLDocument(aGlobal, aError); 21128 if (aError.Failed()) { 21129 return nullptr; 21130 } 21131 21132 // Step 4. Parse HTML from a string given document and compliantHTML. 21133 // TODO(bug 1960845): Investigate the behavior around <noscript> with 21134 // parseHTML 21135 aError = nsContentUtils::ParseDocumentHTML( 21136 *compliantString, doc, 21137 /* aScriptingEnabledForNoscriptParsing */ sanitize); 21138 if (aError.Failed()) { 21139 return nullptr; 21140 } 21141 21142 if (sanitize) { 21143 // Step 5. Let sanitizer be the result of calling get a sanitizer instance 21144 // from options with options and false. 21145 nsCOMPtr<nsIGlobalObject> global = 21146 do_QueryInterface(aGlobal.GetAsSupports()); 21147 RefPtr<Sanitizer> sanitizer = Sanitizer::GetInstance( 21148 global, aOptions.mSanitizer.Value(), /* aSafe */ false, aError); 21149 if (aError.Failed()) { 21150 return nullptr; 21151 } 21152 21153 // Step 6. Call sanitize on document with sanitizer and false. 21154 sanitizer->Sanitize(doc, /* aSafe */ false, aError); 21155 if (aError.Failed()) { 21156 return nullptr; 21157 } 21158 } 21159 21160 // Step 7. Return document. 21161 return doc.forget(); 21162 } 21163 21164 // https://wicg.github.io/sanitizer-api/#document-parsehtml 21165 /* static */ 21166 already_AddRefed<Document> Document::ParseHTML(GlobalObject& aGlobal, 21167 const nsAString& aHTML, 21168 const SetHTMLOptions& aOptions, 21169 ErrorResult& aError) { 21170 // Step 1. Let document be a new Document, whose content type is "text/html". 21171 // Step 2. Set document’s allow declarative shadow roots to true. 21172 RefPtr<Document> doc = CreateHTMLDocument(aGlobal, aError); 21173 if (aError.Failed()) { 21174 return nullptr; 21175 } 21176 21177 // Step 3. Parse HTML from a string given document and html. 21178 // TODO(bug 1960845): Investigate the behavior around <noscript> with 21179 // parseHTML 21180 aError = nsContentUtils::ParseDocumentHTML( 21181 aHTML, doc, /* aScriptingEnabledForNoscriptParsing */ true); 21182 if (aError.Failed()) { 21183 return nullptr; 21184 } 21185 21186 // Step 4. Let sanitizer be the result of calling get a sanitizer instance 21187 // from options with options and true. 21188 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 21189 RefPtr<Sanitizer> sanitizer = Sanitizer::GetInstance( 21190 global, aOptions.mSanitizer, /* aSafe */ true, aError); 21191 if (aError.Failed()) { 21192 return nullptr; 21193 } 21194 21195 // Step 5. Call sanitize on document with sanitizer and true. 21196 sanitizer->Sanitize(doc, /* aSafe */ true, aError); 21197 if (aError.Failed()) { 21198 return nullptr; 21199 } 21200 21201 // Step 6. Return document. 21202 return doc.forget(); 21203 } 21204 21205 void Document::GetAllInProcessDocuments( 21206 nsTArray<RefPtr<Document>>& aAllDocuments) { 21207 for (Document* doc : AllDocumentsList()) { 21208 aAllDocuments.AppendElement(doc); 21209 } 21210 } 21211 21212 } // namespace mozilla::dom